Programacion de micrococontroladores en c

Page 1


PROGRAMACIÓN EN C.

1.- ESTRUCTURA DE UN PROGRAMA EN C

Todo programa en C orientado a microcontroladores cumple con la siguiente estructura:

1.1.- CABECERA. En la cabecera se incluyen características importantes para el manejo del PIC, las cuales son: -

Código del microcontrolador.

-

Configuración de Fusibles.

-

Frecuencia de oscilación.

-

Renombramiento de pines.

1.1.1.- CÓDIGO DEL MICROCONTROLADOR. Para incluir el microcontrolador con el cual se va a trabajar se debe hacer uso de la directiva #include. Archivos (conocidos como header File) que posean extensión .h. Estos archivos contienen información sobre funciones sus argumentos, el nombre de los pines de un modelo 2


determinado de PIC o cualquier otra herramienta que con frecuencia en nuestros programas. Sintaxis: #include <archivo> Ejemplo #1: #include <16f84A.h> Donde “<16f84A.h>” es el microcontrolador que queremos utilizar. Por convención, se utiliza la extensión .h para los archivos de cabecera. Algunos de los microcontroladores más usados y comunes en el mercado son: -

PIC 12F629

(8 Pines).

-

PIC 16F628A (16 Pines).

-

PIC 16F873A (28 Pines).

-

PIC 16F877A (40 Pines).

-

PIC 18F4550 (40 Pines).

1.1.2.- CONFIGURACIÓN DE FUSIBLES. Los fusibles son características, que posee cada microcontrolador y varían de un modelo a otro. Todos los microcontroladores tienen 2 tipos de configuraciones comunes, y que son indispensables que aparezcan, las cuales son: -

Tipo de oscilador.

-

Configuracion del Wathcdog.

1.1.2.1.- TIPOS DE OSCILADOR: a) LP: Oscilador LP (Low Power). Se usa cuando el PIC va a trabajar con un cristal de baja potencia. (Fosc < 200 Khz). b) XT: Oscilador XT. Debe utilizarse cuando el PIC Trabajará con un cristal o resonador de frecuencias iguales o menores a 4Mhz. (Fosc <= 4Mhz). c) HS: Oscilador HS (High Speed). Para cristales o resonadores con frecuencias mayores a 10Mhz. (Fosc >10Mhz)

3


d) RC:Oscilador RC (Resistor/Capacitor). Se usa cuando el PIC va a operar con un circuito RC. 1.1.2.2.- CONFIGURACIÓN WATCHDOG. ¿Qué es el Watchdog? El Watchdog (“Perro Guardian”) o WDT es un temporizador que una vez alcanzado su tiempo límite puede provocar un reset en el PIC. El Watchdog cuenta cada ciertos pulsos de reloj en un determinado tiempo, esperando algún evento generado por el programa, si este evento no ocurre el Watchdog se activa y hace que todo empiece de nuevo debido a que el programa a realizado una acción no prevista. Para activar o desactivar el Watchdog se usan las siguientes sentencias: NOWDT:

No Watch Dog Timer. Deshabilitación del Watchdog.

WDT: Watch Dog Timer. Habilitación del Watchdog. Las sentencias para indicar el tipo de oscilador y la configuración del Watchdog se escriben en una sola línea, escribiendo la directiva #fuses. #fuses: Permite modificar el Valor de los fusibles del microcontrolador que estamos utilizando. Los valores posibles dependen de cada microcontrolador en particular. Sintaxis: #fuses Opciones Opciones es una lista de las opciones posibles separadas mediante comas. Algunos valores comunes son: Tipo de oscilador: LP, XT, HS, RC. WatchDog Timer: WDT, NOWDT. Protección de código: PROTECT, NOPROTECT.

4


Ejemplo #2: #fuses XT,NOWDT Indica que el tipo de oscilador utilizado es del tipo XT (Fosc <= 4Mhz) y el Watchdog está deshabilitado. 1.1.3.- FRECUENCIA DE OSCILACIÓN. Las instrucciones en el microcontrolador necesitan de un ciclo de máquina para ejecutarse y este depende de la frecuencia de oscilación del cristal externo, o una fuente de reloj interna. Se recomienda usar Cristales o resonadores para tener precisión, es muy útil cuando se desea trabajar con más de un microcontrolador y se desea que estén sincronizados. 1 Ciclo de Maquina = 4 Ciclos de reloj Tcm = 4 / Fosc = 4 * Tosc Donde: Tcm : Ciclo de máquina. Fosc: Frecuencia de oscilación del cristal, resonador, o circuito RC. Tosc: Periodo de oscilación del cristal , resonador, o circuito RC. Ejemplo #3: Calcular el ciclo de máquina para una frecuencia de oscilación de 4Mhz. Fosc = 4Mhz Tosc = 1 / 4Mhz = 0,25us = 0,26 x 10-6 segundos Tcm = 4 * 0,25us = 1us Cada instrucción en el microcontrolador se ejecutará en 1us. Para incluir la frecuencia de oscilación con la que se va a trabajar se usa la directiva #use delay. Esta directiva indica al compilador la frecuencia del procesador, en ciclos por

5


segundo, a la vez que habilita el uso de las funciones delay_ms() y delay_us(), las cuales se usan para hacer retardos en milisegundos y microsegundos respectivamente. Sintaxis: #use delay (clock= Frecuencia) Ejemplo #3: #use delay (clock=4000000) En este caso se indica que la frecuencia de oscilación con la que se va a trabajar es de 4Mhz. 1.1.3.1.- FUNCIONES DE RETARDOS 

DELAY_MS(time)

Esta función realiza retardos del valor especificado en time. Dicho valor de tiempo es milisegundos y el rango es 0 – 65535. Ejemplo #4a: delay_ms(1000); 

//retardo de 1 segundo

DELAY_US(time)

Esta función realiza retardos del valor especificado en time. Dicho valor de tiempo es microsegundos y el rango es 0 – 65535. Ejemplo #4b: delay_us(50);

//retardo de 50 microsegundos

1.1.4.- RENOMBRAMIENTO DE PINES. Para el renombramiento de pines se usa la directiva #define. La instrucción #define tiene la siguiente forma: #define <Label> value <Label> es la etiqueta que usaremos en nuestro programa. Y value es el valor que estamos asignando a la etiqueta. 6


Sintaxis: (Cambiar el nombre de un pin) #define Nombre pin_xn Donde: x: Letra que indica el puerto n: Número de pin (0 a 7) Ejemplo #5: #define led1 pin_b0

// El pin B0 ahora se llama Led1

1.2.- PROGRAMAS O FUNCIONES. Las funciones son un conjunto de instrucciones. Pueden existir muchas funciones las cuales deben ser definidas antes de ser usadas, siempre existirá una función principal llamada main(). La función principal Siempre debe existir, e indica el punto en el que comenzara a funcionar el programa. 1.3.- INSTRUCCIONES Las instrucciones o sentencias indican como se debe comportar el PIC, este realizará solo aquello que se le indique. 1.4.- COMENTARIOS Los comentarios facilitan la comprensión del programa, describiendo el funcionamiento de cada línea de código. Los comentarios son ignorados cuando se compila el código. Existen dos formas de hacer comentarios: 1.4.1.- COMENTARIOS DE UNA LINEA Para escribir comentarios que ocupen una sola line se hace uso del doble Slash (//) Ejemplo #6: // Este comentario ocupa una sola línea

7


1.4.2.- COMENTARIOS QUE OCUPAN MÁS DE UNA LINEA. Los comentarios que ocupan más de una línea se escriben de la siguiente manera: /*

Linea de comentario 1 Linea de comentario N */

/*: Indica que se inicia el comentario */: Cierre del comentario Ejemplo #7: Estructura de un programa en C.

#include <16f84A.h>

//Microcontrolador a utilizar

#fuses xt,nowdt

//Oscilación del cristal <= 4mhz //Whatchdog desactivado

#use delay (clock=4000000) //Frecuencia del cristal de 4Mhz void main(){ Sentencias;

// Inicio Función Principal // Instrucciones. Estructuras de control

} // Fin de la Función Principal Seguramente se estará preguntando el significado de “void”, las llaves { }, y para que sirve el punto y coma (;). Void es una palabra reservada en lenguaje C. En nuestro caso tenemos lo siguiente: void main() quiere decir que la función main() no devolverá parámetros o valores. Más Adelante se explicaran en detalle aspectos importantes sobre las funciones. USO DE LAS LLAVES {. . .} C es un lenguaje estructurado o modular en el que un programa está formado por “bloques”. Los elementos que componen un bloque deben estar relacionados entre sí. Lo que indica que lo encerramos entre llaves. {

// INICIO DEL BLOQUE

}

// FINAL DEL BLOQUE 8


USO DEL PUNTO Y COMA (;) El punto y coma indica el final de una sentencia en C. En el Ejemplo #7 generalizamos las instrucciones escribiendo: Sentencia 1; Sentencia 2; O también se pudo haber escrito: Sentencia 1; Sentencia 2; Ambas formas son validas, el final de una sentencia y el inicio de la otra son indicados con el punto y coma (;). 2. DATOS. TIPOS DE DATOS Un dato es toda información que almacena el ordenador. 2.1 TIPOS DE DATOS En cualquier lenguaje de programación siempre existirán 4 tipos de datos fundamentales, estos son: i)

Numéricos.

ii)

Reales.

iii)

Alfanuméricos. a. Letras. b. Números. c. Caracteres especiales. d. Una mezcla de todo lo anterior.

iv)

Booleanos. Solo pueden tener 2 valores, verdadero o falso.

El compilador CCS C acepta los siguientes tipos de datos.

9


3.- CONSTANTES

4.- VARIABLES. Las variables son zonas de la memoria RAM que se utilizan para almacenar datos; se deben declarar antes de ser utilizadas; para ello se debe indicar el nombre y el tipo de dato que se manejarรก. Sintaxis: Tipo_Dato Nombre_variable; Ejemplo #8: int Numero; Float Voltaje;

10


Al declarar las variables se le pueden asignar valores iniciales, de no hacerlo, estas tendrán un valor por defecto igual a cero (0). int Valor = 25; Float Presion = 4.5; Podría darse el caso, en el cual se declaren variables del mismo tipo, el lenguaje C nos brinda una forma elegante de hacerlo. Int Numero_1, Numero_2 , Valor = 25; Float Voltaje, Presion = 4.5; 4.1.- IDENTIFICADORES. Los identificadores o nombres de variables pueden estar formados por letras números o guion bajo ( _ ) y deben comenzar por letra o guion bajo. No deben tener espacios. El guión bajo es el único carácter especial permitido para asignar el nombre de una variable o función, no se permite la letra “ñ” ni las letras acentuadas. Lenguaje C diferencia entre mayúsculas y minúsculas, esto quiere decir que una variable llamada “Numero” será diferente de una llamada “numero”. A continuación unos ejemplos de Nombres no válidos para las variables. Ejemplos #9: Int 2Caras;

// Empieza por un numero (Error)

Float Primer Numero; // Contiene un espacio (Error) Int16 Año;

// La letra “ñ” (Error)

Long Más_Personas;

// Tiene una vocal acentuada (Error)

11


5.- OPERADORES 5.1- ASIGNACIÓN.

5.1- ARITMÉTICOS.

5.1.1- INCREMENTO Y DECREMENTO. Sintaxis: Forma 1: Notación normal. A = A + 1; A = A – 1; Forma 2: Notación compacta. A++; 12


A--; En resumen: A++ es lo mismo que A = A + 1. A-- es lo mismo que A = A - 1. 5.1.1.1- Postincremento. B = A++; Se asigna a B el valor de A y se incrementa el valor de A tras asignar su valor. Por lo tanto si A = 1, entonces: B=1; // Se asigna el valor de A (B = A = 1) A=2; // Se incrementa el valor de A 5.1.1.2- Preincremento. B = ++A; Primero se incrementa el valor de A y despuĂŠs asigna su valor. Por lo tanto si A = 1, entonces: A = 2; // Se incrementa el valor de A B = 2; // Se asigna el valor de A (B = A = 2) El Predecremento y el Postdecremento funcionan de la misma manera. 5.2 RELACIONALES.

13


5.3 LÓGICOS.

5.4 DE BITS.

6.- FUNCIONES. Las funciones tienen la siguiente forma: Nombre_funcion() {

// Cuerpo de la función

Instrucción 1; Instrucción 2; . . Instrucción n; } Las funciones terminan y regresan automáticamente al procedimiento que las llamó cuando se encuentra la ultima llave}.Se deben declarar las funciones antes de utilizarlas de igual manera como se hace con las variables.

14


6.1.- PROTOTIPOS DE FUNCIONES Un prototipo es una declaración de una función. Consiste en una presentación de la función con la misma estructura que la definición, pero sin cuerpo y termina con un “;”. El prototipo le indicara al compilador el tipo de datos que la función regresara y el tipo de parámetros que la función espera. Sintaxis: Tipo_Funcion nombre_funcion(); Donde “Tipo” es cualquiera de los tipos de variables soportados en CCS. Ejemplo #10: Long Ejemplo(); 6.2.- PARÁMETROS. Además de determinar el tipo de resultado que devolverá la función, en el prototipo podemos especificar que parámetros recibirá. Y de que tipo serán. La forma de hacerlo es la siguiente: Tipo_Funcion nombre funcion(Tipo variable1,Tipo variable2,…); 6.3.- DEFINICION DE UNA FUNCIÓN. Luego de haber indicado el prototipo de la funcion, esta debe ser definida luego de la función principal. Una función con parámetros podría ser la siguiente: double Division(float x,float y){ // Cuerpo de la funcion double Resultado; x = 10; y = 250; resultado = x/y; return resultado /* la función retorna el valor de de la variable resultado*/ }

15


6.4.- VOID Significa que la función no devolverá ningún parámetro. Supongamos que la función Ejemplo_x() no debe regresar ningún valor luego de ser llamada. Su prototipo debería ser como sigue: void Funcion_1(); Además, podemos usar void para indicar que la función no recibe parámetros, ni devuelve ningún valor. void Funcion_2(void); ó void Funcion_2(); 6.5.- LLAMADA DE UNA FUNCIÓN. Cuando un programa llama a una función, la ejecución del programa se trasfiere a dicha función el programa retorna a la sentencia posterior a la llamada cuando se acaba la función. Sintaxis: Nombre(Parámetros);

/* Los parámetros son opcionales Dependen de la función*/

Nombre es el identificador con el que es definida la función a la que queremos llamar. Parámetros es la lista de valores que se asigna a cada parámetro de la función(en caso de que tenga, estos se separan por comas). Ejemplo #11: Tiempo(); /*Llamada de la función Tiempo la cual no recibe Parámetros*/

16


7.- ESTRUCTURAS DE CONTROL. Las estructuras de control se utilizan para tomar decisiones, realizar tareas repetitivas o de bucle; son la base de cualquier lenguaje de programación, por lo tanto, su dominio es fundamental. El inicio y el cierre de las estructuras de control se indica haciendo uso de las llaves { }, de forma idéntica como se hace con las funciones. Las estructuras o declaraciones de control que admite CCS son:  If - Else.  While.  Do-While  For.  Switch - Case.  Continue-Break-Goto. 7.1.- ESTRUCTURAS ALTERNATIVAS. 7.1.1.- IF: (“Si. . . entonces. . .”). Con esta estructura se pueden tomar decisiones. Sintaxis: If(Condición){

// Inicio de la estructura IF

Sentencias; } // Fin del IF Ejemplo #12: If(B1 == 0){ // Si B1 es igual a 0 entonces . . . B2 = 10; B3 = 5; } Haciendo uso de los operadores lógicos podemos escribir cosas como: If((B1 == 0)&&(C==1)){ Sentencias;

//Operación AND: Se deben cumplir //Las 2 condiciones*/

} 17


O también: If((B1 == 0)||(C==1)){ Sentencias;

// Operación Or: Se debe cumplir // Alguna de las 2 condiciones

} 7.1.2.- ELSE : (“En Caso contrario . . .”) Esta estructura se usa siempre con un if la cual indica que si la condición del if no es verdadera entonces se ejecutan las sentencias del bloque else. Sintaxis: If(Condición){

// Inicio de la la estructura IF

Sentencias; }// Fin del IF Else{ // Inicio de la la estructura Else Mas_Sentencias; }// Fin del else Ejemplo #13: If(A == 1){ // Si A es igual a 1 entonces . . . B = 2; C = 7; } Else{ En caso contrario: A no es igual a 1 entonces . . . B = 0; C = 0; } 7.1.2.- ELSE IF. Se pueden enlazar los if usando else para decir, si no se cumple esta condición, ve si se puede ejecutar esta otra.

18


Sintaxis: If(Condición){

// Inicio de la la estructura IF

Sentencias; }// Fin del IF Else if(Condición){

// Inicio del Else If

Sentencias; }

// Fin Else If

Else{

// Ninguna de las condiciones se cumple. Sentencias;

}

// Fin del else

7.1.1.- SWITCH – CASE. Si queremos preguntar por varias condiciones, sería muy pesado tener que hacerlo con muchos if seguidos. Sintaxis: switch(Variable){ Case valor1: Sentencias; break; Case valor2: Sentencias; break; Case valor3: Sentencias; break; default: Sentencias; }

19


Donde Variable es la variable por la cual vamos a preguntar, valor1, valor2 y valor3 son los posibles valores de las variables. La palabra reservada break indica que cuando el compilador ejecute esa línea, se saldrá automáticamente de la estructura switch, de no escribir esa sentencia, el programa seguirá preguntando por el resto de las condiciones y ejecutando las instrucciones hasta que finalice la estructura, se cumpla una condición , o se encuentre con la instrucción break. La sentencia default es opcional (no es necesario colocarla) y es la opción que se ejecutará si ninguno de los valores coincide, por lo tanto default, es la opción que se ejecutara por defecto. Ejemplo #14: switch(X){ Case 5: Y = 1; break; Case 10: Y = 2; break; Case 15: Y = 3; break; default: break; } 7.2.- ESTRUCTURAS REPETITIVAS. 7.2.1.- FOR. For se usa para repetir sentencias un número determinado de veces.

20


Sintaxis: For(inicialización ; condición de finalización ; inc. ó dec.){ Sentencias; } Inicialización es una variable que definirá el punto desde el cual comenzará a incrementarse la estructura. La condición de finalización, indica hasta donde se ejecutará el bucle. Por último, la expresión de incremento o decremento modifica la variable de control después de ejecutar el bucle. Si se ejecuta la siguiente expresión se consigue un bucle sin fin. For( ; ; ){ Sentencias; } Ejemplo #15: For(i=0;i<=15;i++){ Printf(“%u”,i); /* Imprime en pantalla los números del 0 al 15.*/ } 7.2.2.- WHILE / DO-WHILE. WHILE se utiliza para repetir sentencias, hasta que una determinada condición se cumple. Sintaxis: while(condición){ Sentencias; } La condición se evalúa y la sentencia se ejecuta mientras la expresión sea verdadera, cuando es falsa se sale del While. Si la expresión es verdadera desde un principio, el bucle While no ejecuta las sentencias ni una sola vez.

21


DO-WHILE se diferencia del WHILE y del FOR en la condición de finalización, la cual se evalúa al final del bucle, por lo que las sentencias se ejecutan al menos una vez. Sintaxis: }do Sentencias; }while(condición);

Si se ejecuta la siguiente expresión se consigue un bucle sin fin. while(1){ Sentencias; } ó }do Sentencias; }while(1);

Ejemplo #16: while(i<=9){ j=4; K=0; } Ejemplo #17: }do j=4; K=0; }while(i<=9);

22


Un bucle WHILE puede comportarse como un bucle FOR, la forma de lograrlo es la siguiente. i=0; while(i<=9){ sentencias; i++; } Lo anterior es equivalente a escribir: for(i=0;i<=9;i++){ sentencias; } 7.3.- OTROS. 7.3.1.- BREAK. Podemos salir de un bucle antes de tiempo con la orden BREAK. Ejemplo #18: For(i=0;i<=10;i++){ If(i == 4){ break; // Sale del bucle } } 7.3.1.- CONTINUE. Podemos saltar alguna repetici贸n de un bucle con la orden CONTINUE. For(i=0;i<=10;i++){ If(i == 4){ Continue; /* El bucle se salta el numero 4 Y continua la ejecuci贸n del bucle*/ } } 23


7.3.1.- GOTO: (“Ir a...”) GOTO permite hacer saltos incondicionales, asignando una etiqueta en algún lugar del código, y cuando se desee regresar al lugar de la etiqueta se hace uso del GOTO la siguiente manera: Inicio: // Etiqueta . . . Goto inicio; Se debe evitar el uso del GOTO en lo posible, ya que genera malos hábitos de programación, se recomienda su uso, cuando se tengan un anidamiento de estructuras repetitivas, de donde es difícil salir con una sola instrucción como BREAK. El siguiente ejemplo muestra un caso en el que es muy útil usar GOTO. Ejemplo#19: for(i=1;i<=100;i++){ // Inicio del For1 for(j=0;j<=50;j++){ // Inicio del For2 for(K=0;K<=9;K++){ // Inicio del For3 if(Z==1){ // Inicio del If goto Seguir; } } } }

// Fin del If

// Fin del For3

// Fin del For2

// Fin del For1

Seguir: Sentencias; En las estructuras anidadas, el bucle mas interno es el que primero termina, en este caso el bucle mas interno es el For3, para poder salir de la estructura se debe salir primero del bucle For3, luego del For2 y finalmente del For1 que es el principal, obviamente con escribir la sentencia break en el bucle for3 solo se conseguiría salir de este, y quedar 24


atrapado en los otros 2 bucles, y es aquí donde la sentencia GOTO se torna realmente útil ya que nos redirige a la zona deseada de nuestro código de un solo salto. NOTA: Jamás se debe usar un GOTO dentro de una función hacia un punto fuera de esta, ya que toda función debe retornar, para continuar su ejecución desde el lugar de donde se llamó.

8.- DIRECTIVAS DE MANIPULACION DE PUERTOS. El compilador CCS posee funciones predefinidas para trabajar con puertos, y pines específicos del microcontrolador. Funciones Para el manejo de puertos: output_X(valor): Saca por el puerto un valor de 8 bits (0 – 255). input_X( ): Función para la lectura del puerto correspondiente. set_tris_X(valor): Carga el registro TRISx con un valor de 8 bits, en esta función se indican

las entradas y las salidas del puerto. Indicando las entradas con 1 y las salidas

del puerto con 0. Donde la X es la inicial del puerto correspondiente (A, B, C , D, E). Funciones Para el manejo de pines. output_low(pin*): Coloca el estado lógico del pin a 0. output_high(pin*): Coloca el estado lógico del pin a 1. output_bit(pin*,Valor): Pin al valor especifico. (Valor puede ser 0 o 1). output_toggle(pin*): Complementa el valor del pin. input(pin*) : Lee el valor del pin.

25


Las funciones input_x() y output_x() dependen de la directiva #USE *_IO que este activa. Las directivas mas utilizadas son: #USE FAST_IO(PUERTO) Con la función output_x() se saca un valor de 8 bit por el puerto indicado y con la función input_x() se lee el valor del puerto. La directiva no modifica el registro TRIS correspondiente. #USE STANDARD_IO(PUERTO) Con la función output_x() el compilador se asegura de que el terminal, o terminales correspondientes, sean de salida mediante la modificación del registro TRIS correspondiente. Con la función input_x() ocurre lo mismo pero asegurando el terminal como entrada. 9.- DISEÑO DE CIRCUITOS CON ISIS DE PROTEUS. Proteus es un poderoso software de simulación y diseño electrónico, de la compañía Labcenter Electronics. El ISIS permite la simulación de las familias de microcontroladores mas conocidas como la: 12F (Gama baja), 16F (Gama media), 18F (Gama alta). Proteus puede simular una gran cantidad de dispositivos digitales y analógicos. En la siguiente imagen se puede apreciar la apariencia del entorno de trabajo de ISIS:

Figura 9-1

26


9.1.- CARACTERISTICAS BÁSICAS PARA SIMULAR. Se debe identificar la paleta de dispositivos, que está a la izquierda de la pantalla, en esta paleta el diseñador debe identificar el botón P como se muestra en la imagen.

Figura 9-2

Al presionar el botón P, el programa abre una nueva ventana que permite la búsqueda de dispositivos por medio de la referencia comercial, o bajo la clasificación que el mismo ISIS tiene.

Figura 9-2

27


El programa genera una lista en la parte derecha de la ventana con los dispositivos relacionados con la solicitud del usuario. Para seleccionar un dispositivo de esta lista se da doble clic sobre cada uno de los numerales de la lista. Para comenzar busque los siguientes dispositivos: PIC16F877A, RES(220ohm), CAP(22pf), CRYSTAL, LED-GREEN. El cristal no es necesario en la simulación, pero es recomendable su uso en el montaje en físico.

Figura 9-3

El paso siguiente es buscar los terminales de GROUND (tierra) y POWER (Vcc). La terminal de Vcc tiene una diferencia de potencial por defecto de 5v. Para colocar estas terminales en le área de trabajo se presionan los ítems en la paleta de terminales y posteriormente en el área de trabajo, después de esta acción en el área de trabajo se debe ver lo siguiente:

Figura 9-4

28


El siguiente paso es unir los componentes en el área de trabajo, para esto se debe presionar el botón COMPONENT MODE (El símbolo es un amplificador operacional amarillo) de la paleta de herramientas a la izquierda. Para unir los dispositivos en el área de trabajo se sigue el mismo procedimiento de las terminales, al finalizar se debe tener la siguiente vista:

Figura 9-5. El circuito más simple con un Microcontrolador.

10.- CREACION DE UN PROYECTO CON CCS. 1) Crear una carpeta donde se guardara el programa y el esquema electrónico diseñado en Proteus. 2) Abrir el compilador CCS.

Figura 10-1. Entorno de Trabajo de CCS.

3) En la barra de herramientas dar clic a la pestaña Proyect. 29


Figura 10-2

4) Click en New/Source File.

Figura 10-3. Creaci贸n de un nuevo proyecto.

30


5) En la carpeta creada escribir el nombre del Programa. Se recomienda que el programa tenga el mismo nombre de la carpeta.

Figura 10-4

6) Finalmente Guardar.

Figura 10-5. Proyecto creado listo para Programar.

Con nuestro proyecto creado procedemos a escribir nuestro primer Programa, el “Hola Mundo� de los microcontroladores. 31


Ejemplo#20: Programa que encienda y apague un led durante un tiempo indefinido. El Parpadeo debe durar 1 segundo. (500ms encendido y 500ms apagado). // Program_00_Apagar_Encender_Led #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) #define led1 pin_b1

// El pin B1 ahora se llamará led1

void main(){

// Funcion Principal

set_tris_b(0b00000000);

// Config. del puerto B como Salida

output_b(0b00000000);

// Apaga todos los Pines del puerto B

while(1){

// Bucle infinito

output_bit(led1,1);

// Enciende el Led

delay_ms(500);

// Espera 500ms

output_bit(led1,0);

// Apaga el Led

delay_ms(500);

// Espera 500ms

} }

// Fin del While // Fin de la función principal

7) Para compilar el programa en la barra de herramientas seleccione La pestaña Compile y luego la opción Compile.

Figura 10-6. Compilación.

32


Si la compilación fue exitosa deberá aparecer una imagen como la siguiente.

Figura 10-7. Compilación Exitosa.

En la compilación se generan archivos con diferentes extensiones, utilizaremos los archivos de extensión *.HEX y *.COF, los cuales se pueden utilizar para trabajar con el entorno PROTEUS. 8) Dar doble clic en el microcontrolador para abrir la ventana de edición.

Figura 10-8

33


7) En el item Processor Clock Frequency se debe colocar la frecuencia de reloj especificada en la programación, para nuestro caso será de 4Mhz.(Figura 10-8). 8) Incluir el archivo de trabajo en el ítem Program File dando clic al símbolo de la carpeta. (Figura 10-8). ´

Figura 10-9

9) Seleccionar el archivo de extensión *.HEX o *.COF. 10) Ahora se puede proceder a la simulación del circuito empleando la barra de simulación. Esta opción se compone de la opción MARCHA, PASO A PASO, PAUSA y PARADA.

Figura 10-10

34


11.- PROYECTOS CON LEDS Ejemplo#21:

Se desea programar un semáforo, de 2 intersecciones que cumpla la

siguiente función: La luz roja del semáforo 1 y la luz verde deben activarse por 9 segundos, luego se debe activar la luz amarilla del semáforo 2 por 3 segundos mientras la luz roja del otro semáforo sigue activa, debe completarse el ciclo para ambos semáforos. Componentes ISIS: PIC16F877A, RX8 (Pack de 8 resistencias), LED-GREEN, LEDRED, LED-YELLOW, CRISTAL, CAP (22pf).

Figura 11-1. Semáforo De 2 Intersecciones con leds.

// Program_01_Semaforo #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) void main(){

// Funcion Principal

set_tris_b(0b00000000);

// Config. del puerto B como Salida

output_b(0b00000000);

// Apaga todos los Pines del puerto B 35


while(1){ output_b(0b00100001);

// Bucle infinito // Rojo_1 = ON / Verde_2 = ON

delay_ms(9000); output_b(0b00010001);

// Rojo_1 = ON / Amarillo_2 = ON

delay_ms(3000); output_b(0b00001100);

// Verde_1 = ON / Rojo_2 = ON

delay_ms(9000); output_b(0b00001010);

// Amarillo_1 = ON / Rojo_2 = ON

delay_ms(3000); } }

// Fin del While // Fin de la funcion Principal

Ejemplo#22: Dise単ar un contador binario de 8 bits que cuente de 0 a 255, al llegar a 255 la cuenta debe reiniciarse. Entre un cambio y otro debe haber un retarde de 300 milisegundos. Componentes ISIS: PIC16F877A, RX8 (Pack de 8 resistencias), LED-BARGRAPHGREEN (Barra de 10 Leds verdes), CRISTAL, CAP (22pf).

Figura 11-2

36


// Program_02_Contador_8_Bits #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) void main(){

// Funcion Principal

set_tris_b(0b00000000);

// Config. del puerto B como Salida

output_b(0b00000000); while(1){ for(i=0;i<=255;i++){

} }

// Apaga todos los Pines del puerto B // Bucle infinito // Bucle for de 0 a 255

output_b(i);

// saca Por el Pto.B el valor de i

delay_ms(300);

// Retardo de 300milisegundos

}

// Fin del Bucle For // Fin del While // Fin de la funcion Principal

37


Ejemplo#23: DiseĂąar un programa que sea capaz de desplazar un bit desde el LSB de un puerto al MSB (de derecha a izquierda) al llegar al MSB este debe realizar un desplazamiento en sentido contrario (De izquierda a derecha), el proceso debe repetirse infinitamente. Mostrar las salidas con leds. Se utilizarĂĄ el esquema electrĂłnico del Ejercicio #22 (Ver Figura 11-2). // Program_03_Desplazamiento #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) int i,j=1; void main(){

// Funcion Principal

set_tris_b(0b00000000); output_b(0b00000000); delay_ms(500); while(1){ for(i=1;i<=7;i++){ // Desplazamiento a la Izquierda output_b(j); // Saca por el pto B el valor de j j <<= 1; // <<= indica desplazamiento a la izq. delay_ms(500); }

// Fin del For de despl. A la izquierda

for(i=1;i<=7;i++){ //Desplazamiento a la Derecha output_b(j); j >>= 1; // >>= indica desplazamiento a la Derecha delay_ms(500); } // Fin del For de despl. A la derecha } // Fin del While } // Fin del main Al ejecutarse el programa es necesario que el bit menos significativo del puerto este activado. Se han declarado 2 variables:

38


int i,j=1; i: se utiliza para indicar la cantidad de veces que se va a desplazar el valor de j (j=1) desde el LSB hasta el MSB, en este caso se desplazara 7 veces (como se indica en el bucle For) hasta llegar a su objetivo, para luego regresar a su punto de origen. j: es la variable que ira tomando los valores que vamos a sacar por el puerto B. for(i=1;i<=7;i++){ // Desplazamiento a la Izquierda output_b(j); // Saca por el pto B el valor de j j <<= 1; // <<= indica desplazamiento a la izq. delay_ms(500); } En el bucle For indicamos la cantidad de veces que se desplazar谩 el valor de j, que por defecto es 1 (00000001 binario) es decir tendremos 7 posibles valores de j al comenzar el desplazamiento. 00000010 (binario) = 2 (decimal) 00000100 (binario) = 4 (decimal) 00001000 (binario) = 8 (decimal) 00010000 (binario) = 16(decimal) 00100000 (binario) = 32 (decimal) 01000000 (binario) = 64(decimal) 10000000 (binario) = 128 (decimal) Podemos darnos cuenta que el desplazamiento a la izquierda no es m谩s que una multiplicaci贸n por 2, es decir en realidad lo que se ejecuta es lo siguiente: j = j*2; Quiere decir que el valor que se tenga en j justo en ese instante, se va a multiplicar por 2, esto se traduce en el desplazamiento de un bit a la izquierda (cuando se saca el valor por el puerto), y se asigna ese nuevo valor a la variable. El desplazamiento a la derecha es una divisi贸n entre 2. j = j/2; Como ejercicio reemplace j <<= 1 por j = j*2 y j >>= 1 por j = j/2, y observe lo que sucede.

39


Ejemplo#24: usando las 8 salidas de un puerto generar una secuencia, similar a las luces de una discoteca, al inicio deben estar en estado alto el bit 0 y el bit 7 del puerto, se desea que estos bits se desplacen hacia su extremo opuesto correspondiente, generando un efecto de cruce cuando pasan por el centro, el proceso debe repetirse indefinidamente. Mostrar las salidas con leds. Se utilizar谩 el esquema electr贸nico del Ejercicio #22 (Ver Figura 11-2). // Program_04_Luces_Para_Discoteca #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) Int Tiempo = 100; // Argumento de la funci贸n delay_ms(Valor) void main(){ // Inicio de la funcion principal set_tris_b(0b00000000); // Config. Del pto.B como salida output_b(0b00000000); // Saca un cero por los pines del pto.B while(1){ // Bucle infinito output_b(0b10000001); delay_ms(Tiempo); output_b(0b01000010); delay_ms(Tiempo); output_b(0b00100100); delay_ms(Tiempo); output_b(0b00011000); delay_ms(Tiempo); output_b(0b00100100); delay_ms(Tiempo); output_b(0b01000010); delay_ms(Tiempo); } // Fin del bucle infinito } // Fin de la funci贸n principal

40


Este programa muestra otra de las posibilidades que nos ofrece la función delay_ms(valor), donde valor podría ser una constante(como se había trabajado en los programas anteriores), o puede ser una variable, la cual podría cambiar su valor en cualquier momento, permitiendo generar temporizaciones de diferente duración. La razón por la cual se usa como argumento una variable es sencilla, simplemente para poder variar la temporización de la secuencia, sin modificar muchas líneas de código. Para este ejemplo el cambio entre una secuencia y otra es de 100ms, pero supongamos que deseamos una temporización de 50ms, en lugar de escribir delay_ms(50); 6 veces basta con modificar el valor de int Tiempo = 50; que es la variable para controlar la temporización, permitiéndonos ganar tiempo valioso, sin hacer que programar secuencias con la misma duración sea tedioso.

12.- PROYECTOS CON PULSADORES Los pulsadores permiten al microcontrolador comunicarse con el mundo exterior, estos mandan valores digitales al PIC permitiendo tomar decisiones en base a ellos. Existen 2 tipos de conexiones para los pulsadores Pull-Up y Pull- Down. Pull-Up: Siempre está en 1 lógico (5V), y cuando se presiona el pulsador este cambia su estado mandando un cero lógico (0V). Pull-Down: Siempre está en 0 lógico (0V), y cuando se presiona el pulsador este cambia su estado mandando un 1 lógico (V).

Figura 12-1

41


Ejemplo#25: Se desea encender un led durante 1 segundo al presionar un pulsador, al pasar este tiempo el led debe apagarse, hasta que se vuelva a presionar el pulsador. Componentes ISIS: PIC16F877A, BUTTON (Pulsador), RES(220ohm), LED-GREEN , CRISTAL, CAP (22pf).

Figura 12-2

// Program_05_Pulsador_Led #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) #define Led pin_b1

// El Pin B1 se llamara Led

#define Pulsador pin_b0 // El Pin B0 se llamara Pulsador void main(){ // Funcion principal set_tris_b(0b00000001); // Config del pin B0 como entrada // y el resto de lo pines como salida output_b(0b00000000); while(1){ // Bucle infinito if (input(Pulsador)==0){ // Si se presiona el pulsador output_bit(Led,1); // Ecender Led 42


delay_ms(1000);

// retardo de un segundo

output_bit(Led,0); // Apagar Led } // Fin del If } // Fin del bucle infinito } // Fin de la funcion principal

En el esquema electr贸nico se hace uso de un pulsador del tipo Pull - up por lo tanto al ser presionado su estado l贸gico cambia de uno a cero, raz贸n por la cual se pregunta por cero en el if y no por uno. if (input(Pulsador)==0){ // Si el pulsador es Pull-up if (input(Pulsador)==1){ // Si el pulsador es Pull-Down

Ejemplo#26: Dise帽ar un programa que permita controlar 3 salidas con un pulsador se debe alternar el funcionamiento de las 3 salidas activando 1 a la vez de forma secuencial es decir, activar la salida 1 luego la 2 y finalmente la 3, el ciclo debe repetirse. Componentes ISIS: PIC16F877A, BUTTON (Pulsador), RES(220ohm), LED--GREEN , CRISTAL, CAP (22pf).

Figura 12-3

43


// Program_06_1Pulsador_3Salidas #include <16f877a.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) #define Pulsador pin_b0 int Flag=1; void main(){ set_tris_b(0b00000001); output_b(0b00000000); while(1){ if (input(Pulsador)==0){ delay_ms(250); // Retardo anti-rebote switch(Flag){ case 1: output_b(0b00000010); // Activo el pin B1 Flag++; // Incremento la badera break; case 2: output_b(0b00000100); // Activo el pin B2 Flag++;

// Incremento la badera

break; case 3: output_b(0b00001000); // Activo el pin B3 Flag = 1 ; break; } // Fin del Switch } //Fin del If } // Fin del While } // Fin de la funcion Principal 44


En este programa se hace uso de la estructura Switch la cual nos permite preguntar por los posibles valores que podría tener una variable, y en función de estos, generar acciones, en este caso hemos asociado al pulsador una variable de nombre Flag (Bandera), que será la variable por la cual vamos a preguntar cada vez que se pulse el botón. Para las siguientes sentencias: case 1: output_b(0b00000010); // Activo el pin B1 Flag++; // Incremento la badera break; En este caso estamos preguntando si la variable es igual a uno (Flag = 1, notese que inicialmente cuando se declaro la variable Flag, se le asigno el valor de 1), de cumplirse esta condición entonces se activa la primera salida ubicada en el pin B1, luego se incrementa la variable en uno (Flag = 2), de forma tal, que cuando se presione el pulsador nuevamente, el programa sepa cuál es el siguiente caso , y que salida debe activar. Con la sentencia break salimos inmediatamente de la estructura switch, de no colocarse, el programa seguiría preguntando por cada una de las condiciones hasta llegar al final de la estructura, haciendo más lenta la ejecución del programa. case 3: output_b(0b00001000); // Activo el pin B3 Flag = 1; break; Cuando se ha activado la salida 3 en el pin B3 se necesita que cuando se presione el pulsador se active la salida 1, para lograr esto colocamos nuevamente la variable a su valor inicial (Flag = 1). Ejemplo#27: Diseñar un Programa que permita controlar 8 salidas con 3 pines del microcontrolador, utilice un decodificador 74LS138, las salidas deben activarse una a una cada vez que se pulse un botón. Componentes ISIS: PIC16F877A, RX8 (Pack de 8 resistencias), LED-BARGRAPHGREEN (Barra de 10 Leds verdes), 74LS138, CRISTAL, CAP (22pf).

45


Figura 12-4

// Program_07_PIC_y_Decodificador #include <16f877a.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(a) #use fast_io(b) #use fast_io(c) //Redefinicion de Puertos #define Pulsador pin_a0 //Definicion de variables int valor; void main(){ set_tris_b(0b00000000); // Conf. Del puerto B como salida set_tris_a(0b00000001); // Conf Del pin A0 como entrada set_tris_c(0b00000000); // Conf. Del puerto C como salida output_b(0b00000000); output_c(0b00000000); 46


while(1){ if (input(Pulsador)== 0){

// Si se presiono el pulsador

output_c(0b00000001)

// Actica el enable del 74LS138

delay_ms(250);

// retarde de 250ms

if(valor>7){

// si valor es mayor que 7

valor=0;

//Coloca a cero la variable

} output_b(valor);

// saca por el puerto B el contenido Valor

valor++;

// Incrementa la variable

} // Fin del If } // Fin del bucle infinito } // Fin de la funcion principal El decodificador 74LS138 es un demultiplexor que con 3 pines nos permite controlar 8 salidas, este es capaz de activar una de las 8 salidas dependiendo de la combinación binaria en sus entradas, si desea conocer mejor el funcionamiento de este circuito integrado consulte su hoja de datos (Datasheet). Inicialmente el decodificador esta desactivado, el Enable en el pin c0 está en estado bajo, cuando se presiona el pulsador este es habilitado y se activa la salida correspondiente. La condición: if(valor>7){ valor=0; } Indica que si la variable es mayor que 7 entonces, esta volverá a su valor inicial. De 0 a 7 se tendrán 8 posibles valores (Ya que controlaremos 8 salidas), esa es la razón por la cual se pregunta por 7 y no por otro número. output_b(valor); valor++; Se ejecuta siempre y cuando se presione el pulsador, en este caso saca el valor de la variable por el puerto y luego incrementa su valor.

47


PROYECTOS PROPUESTOS CON LEDS. 1. Encienda un led conectado en el pin RB0 durante 1 segundo, luego ap谩guelo por 500ms. El proceso debe repetirse 4 veces, luego el led debe permanecer apagado. 2. Encienda 2 leds conectados en RB0 y RB1 alternamente, es decir mientras un led esta encendido el otro permanece apagado. Los tiempos de transici贸n son de 500ms, entre encendido y apagado. El proceso debe continuar indefinidamente. 3. Genere 5 parpadeos de un led con intervalos de 400ms, luego haga 2 parpadeos de 1 segundo con un segundo led, luego haga que los 2 leds parpadeen 3 veces, repita el proceso indefinidamente.

PROYECTOS PROPUESTOS CON PULSADORES. 4. Haga un proyecto en el que al presionar un bot贸n este encienda un led intermitente de 8 repeticiones de 100ms.Luego el led permanezca apagado, y el programa vuelve a sensar el pulsador. 5. Con un pulsador haga que 8 leds conectados en el puerto B, se enciendan, de derecha a izquierda uno a la vez, empezando de RB0 a RB7, al final este ultimo permanece encendido, con otro pulsador haga que los leds se desplacen uno a uno hacia la derecha, es decir, de RB7 que fue el ultimo y que est谩 actualmente encendido se desplace hasta RB0 las pausas son de 200ms.

48


13.- PROYECTOS CON DISPLAYS Una de las aplicaciones de los leds son los displays, estos son dispositivos electrónicos utilizados generalmente para visualizar datos numéricos, pero también se pueden visualizar letras o palabras, si se hace el arreglo adecuado colocando uno tras otro. Existen displays en el mercado de varios tipos: Displays de 35, 16 y 14 segmentos, además displays de 7 segmentos del tipo ánodo o cátodo común. En este curso estudiaremos los displays de 7 segmentos que son los más comunes y fáciles de manejar.

Figura 13-1

13.1.- DISPLAY CÁTODO COMÚN. Cátodo común significa que el cátodo de cada uno de los leds que conforman el display se encuentran conectado entre sí, y cada uno de los segmentos se prendera con niveles de voltaje altos es decir 1L.

Figura 13-2

49


13.2.- DISPLAY ÁNODO COMÚN. Ánodo común significa que el ánodo de cada uno de los leds que conforman el display se encuentran conectado entre sí, y cada uno de los segmentos se prenderá con niveles de voltaje altos es decir 0L.

Figura 13-3

Realizaremos una práctica que permita visualizar los números del 0 al 9 en un display de 7 segmentos del tipo ánodo común. Se debe generar una tabla para identificar cual es la lógica binaria para encender uno de los segmentos que identifica cada número.

Figura 13-4.

Los segmentos se encenderán con niveles de voltaje bajos (0L) ya que estamos usando un display tipo ánodo común. A continuación veremos la forma más fácil de visualizar números en un display, y es mediante el circuito integrado 7447 si el display es ánodo común, o el 7448 si el display es cátodo común. Lo que realizan estos integrados es convertir el código binario de entrada en el código de salida para cada uno de los números que se encuentran en la tabla para poder visualizarlos. Ejemplo#28: Diseñar un contador de 0 a 9 utilizando el decodificador 7447. La salida debe ser mostrada en un display ánodo común. El proceso debe repetirse indefinidamente.

50


Componentes ISIS: PIC16F877A, RX8 (Pack de 8 resistencias), 7SEG-COM-ANODE, 74LS47, BUTTON, RES(10K) ,CRISTAL, CAP (22pf).

Figura 13-5.

// Program_08_Contador_de_0_a_9 #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) int i; void main(){ // Funcion Principal set_tris_b(0b00000000); output_b(0b00000000); while(1){ // Bucle infinito for(i=0;i<=9;i++){ // Bucle For de 0 a 9 output_b(i);

// Saca por el Puerto b el valor de i

delay_ms(1000); } // fin del Bucle For } // Fin del bucle infinito } // Fin de la funci贸n principal 51


Ejemplo#29: Diseñar un contador de 0 a 9 sin 7447. La salida debe ser mostrada en un display ánodo común. El proceso debe repetirse indefinidamente. Componentes ISIS: PIC16F877A, RX8 (Pack de 8 resistencias), 7SEG-COM-ANODE, BUTTON, RES(10K) ,CRISTAL, CAP (22pf).

Figura 13-6.

// Program_09_Contador_de_0_a_9_sin_7447 #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) void main(){ set_tris_b(0b00000000); output_b(0b00000000); while(1){ output_b(0b1000000); // 0 delay_ms(1000); output_b(0b1111001); // 1 delay_ms(1000);

52


output_b(0b0100100); // 2 delay_ms(1000); output_b(0b0110000); // 3 delay_ms(1000); output_b(0b0011001); // 4 delay_ms(1000); output_b(0b0010010); // 5 delay_ms(1000); output_b(0b0000011); // 6 delay_ms(1000); output_b(0b1111000); // 7 delay_ms(1000); output_b(0b0000000); // 8 delay_ms(1000); output_b(0b0011000); // 9 delay_ms(1000); } // Fin del bucle infinito } // Fin de la función principal Nota: La combinación binaria arrojada por el puerto B corresponde a la tabla de la figura 13-4. Ejemplo#30: Diseñar un programa, que permita, con un Dip-Switch de 4 interruptores, realizar combinaciones binarias del 0 al 9. El valor binario debe ser mostrado en un display solo después de haber presionado el botón de “Start”.

53


Componentes ISIS: PIC16F877A, RX8 (Pack de 8 resistencias), 7SEG-COM-ANODE, BUTTON, DIPSW4, 5 RES(10K) , 1 RES(330) , LED-GREENCRISTAL, CAP (22pf).

Figura 13-7.

// Program_10_Display_Dip_Switch #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) #use fast_io(d) #define Start pin_d4

// El pin d4 ahora se llama Start

int puerto; void main(){ set_tris_b(0b00000000); set_tris_d(0b00001111); output_b(0b00001111); while(1){ puerto = input_d() & 0b00001111; // Operacion AND de 8 Bits if(input(Start)==0){ // Si se presiona el boton de Start 54


delay_ms(250);

// retardo anti-rebote

output_b(puerto); // Saca el valor obtenido por el puerto } // Fin del if } // Fin del bucle infinito } // Fin de la función principal Se ha declarado una única variable: int puerto; Esta variable tendrá dos funciones, la primera será almacenar el valor capturado por el puerto D producto de la combinación del Dip-Switch , y la segunda será mediante la función output_b(puerto), donde la variable puerto es el valor que se ha enviado a las salidas del puerto b que para su representación en display. En el puerto d se tienen conectadas 5 entradas pero solo nos interesan 4, para ello se ha utilizado una operación AND en el puerto d de la siguiente manera: puerto = input_d() & 0b00001111; Donde puerto será igual a la lectura obtenida del puerto D. Como solo nos interesan 4 pines de este puerto, se aplica una operación AND de 8 bits. El valor a la derecha del símbolo & (AND) indica que de esos 8 bits del valor 0b00001111, los unos (1) serán los valores que tomaremos de los pines que nos interesan, y los 4bits más significativos, serán limpiados, ya que no son de nuestro interés, esta técnica es muy útil cuando se tienen entradas que van a realizar otra función y no deseamos que estas alteren las operaciones que deseamos realizar con el microcontrolador. Finalmente luego de obtener la combinación binaria del Dip-Switch se debe sensar el botón de Start para saber cuando este es presionado, para mostrar el valor en display. Esto se consigue de la siguiente manera: if(input(Start)==0){ // Si se presiona el boton de Start delay_ms(250);

// retardo anti-rebote

output_b(puerto); // Saca el valor obtenido por el puerto } // Fin del if Como cuarto y último proyecto con displays se realizará un contador digital de 0 a 99 utilizando un solo decodificador 7447. Ejemplo#31: Diseñar un contador de 0 a 99, mostrar las salidas en 2 displays ánodo común, se debe incrementar el conteo cada segundo. El Proceso debe repetirse indefinidamente.

55


Componentes ISIS: PIC16F877A, RX8 (Pack de 8 resistencias), 7SEG-MPX2-CA, BUTTON, RES(10K) , 1 RES(330) , LED-GREENCRISTAL, CAP (22pf).

Figura 13-8.

En el diagrama, los pines comunes de los displays se encuentran conectados a 2 bits del microcontrolador, el cual activará primeramente un display y luego activará el otro display. Si la pausa entre la activación del primer display con el segundo es muy pequeña (5 milisegundos), el ojo humano visualizará como si estuvieran prendidos los 2 displays al mismo tiempo.

56


// Program_11_contador_de_0_a_99 #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) int Decenas,Unidades,Pausa; void main(){ set_tris_b(0b00000000); set_tris_a(0b00000000); output_b(0b00001111); output_b(0b00000000); while(1){ // Inicio del bucle infinito For(Decenas=0;Decenas<10;Decenas++){ // Inicio: For (Dec.) For(Unidades=0;Unidades<10;Unidades++){//Inicio: For (Uni.) For(Pausa=1;Pausa<=100;Pausa++){// Inicio: Pausas output_a(0b00000001); // Disp. unidades On output_b(Unidades); delay_ms(5); output_a(0b00000010); // Disp. Decenas On output_b(Decenas); delay_ms(5); }// Fin: For para controlar Pausas }// Fin: For (Unidades) }// Fin: For (Decenas) }// Fin del bucle infinito }// Fin de la funcion principal Para hacer que el incremento sea de un segundo usamos un bucle for de la siguiente manera:

57


For(Pausa=1;Pausa<=100;Pausa++){// Inicio: Pausas output_a(0b00000001); // Disp. unidades On output_b(Unidades); delay_ms(5); output_a(0b00000010); // Disp. Decenas On output_b(Decenas); delay_ms(5); }// Fin: For para controlar Pausas Dentro de este bucle, el tiempo total que estarán en funcionamiento ambos displays será de 10 milisegundos, por lo tanto para lograr incrementar el valor del contador cada segundo el bucle se debe repetir 100 veces (Condición de finalización) antes de cambiar, es decir 10 milisegundos multiplicados por 100 nos dará 1 segundo. Durante un segundo ambos displays mantendrán el mismo valor de la variable asociada a cada display. PROYECTOS PROPUESTOS CON DISPLAYS 1) Diseñar un proyecto en el cual, cada vez que se presione un pulsador este se incremente, el rango debe ser de 0 a 9. Mostrar la salida en display. 2) Diseñar un contador decreciente de 99 a 0. El proceso debe repetirse indefinidamente. 3) Con el esquema de la figura 13-7 diseñar un circuito que muestre en display solo los números pares producto de la combinación binaria del Dip-Switch, si el número es impar el display debe mostrar cero. La combinación binaria del Dip-Switch debe ser representada con leds indiferentemente de si se trata de un número par o impar, se puede hacer uso de un botón de “Start”, para indicar, cuándo se muestra el resultado en display y con los leds. Sugerencia: Use el puerto C para mostrar la combinación binaria del Dip-Switch con leds.

58


14.- MANEJO DE UN MÓDULO LCD Un LCD (Liquid Crystal Display), pantalla de cristal líquido, es una de las herramientas mayormente utilizadas para desplegar algún tipo de información (voltaje, temperatura, presión, entre otros). En el mercado existe gran variedad de módulos LCD, los que permiten realizar gráficos (GLCD), los alfanuméricos 16x2, 16x4, 8x2 con blackligtht y sin blackligtht. Para este curso estudiaremos los módulos LCD 16x2 ya que son los más utilizados en los proyectos electrónicos. 14.1.- LCD ALAFANUMERICO 16x2

Figura 14-1.

Este tipo de LCD, permite visualizar datos hasta 16 caracteres por 2 líneas, cada carácter está compuesto por una matriz de leds, permitiendo formar cualquier letra que se le asigne desde el microcontrolador.

Figura 14-2.

59


El bus de datos es de 8 bits (D0 a D7), aunque también existe la posibilidad de trabajar con 4 bits (D4 a D7). El compilador C incluye un fichero que permite trabajar con un LCD. El archivo es LCD.C y debe llamarse como un #include. Este archivo dispone de varias funciones ya definidas: Lcd_init(); Es la primera función que debe ser llamada. Borra el LCD y lo configura con el formato de 4 bits, con dos líneas y con caracteres de 5x8 puntos, en modo encendido, cursor apagado y sin parpadeo. lcd_gotoxy(byte x, byte y); Indica la posición de acceso al LCD. Por ejemplo, (1,1) indica la primera posición de la primera línea. lcd_getc(byte x, byte y); Lee el character de la posicion (x,y). lcd_putc(char S); S es una variable de tipo char. Esta función escribe la variable en la posición correspondiente. Si además se indica: \f: Se limpia el LCD. \n: El cursor va a la posición (1,2). \b: El cursor retrocede una posición. El compilador de C ofrece una función más versátil, para trabajar con el LCD: printf(string) printf(cstring,values) printf(fname,cstring,values) String es una cadena o un array de caracteres, values una lista de variables separadas por comas y fname una función. El formato para indicar que aparecerá una variable es %nT, donde n es opcional y puede ser: 1-9: para especificar cuantos caracteres deben aparecer. 01-09: Para indicar la cantidad de ceros a la izquierda. 1.1-9.9: Para indicar coma flotante. 60


T puede indicar:

El driver LCD.C estรก pensado para trabajar con el PORTD o El PORTB. Por defecto, utiliza el PORTD a menos que indiquemos lo contrario mediante: #define use_portb_lcd True Ejemplo#32: Mostrar un mensaje, que ocupe las 2 lineas de la pantalla LCD 16x2. Componentes ISIS: PIC16F877A, BUTTON, RES(10K), CAP (22pf), CRYSTAL, LM016L(LCD 16x2), POT-HG(10K).

Figura 14-3.

61


// Program_12_LCD #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #define use_portb_lcd TRUE #include <LCD.c> // Driver para el manejo de la LCD void main(){ // Funcion Principal lcd_init(); // Inicializacion de las funciones para la LCD delay_us(50); while(1){ // Bucle infinito printf(lcd_putc,"\f

<<UNEXPO>>\n");

printf(lcd_putc," ING.MECATRONICA"); delay_ms(100); // la pantalla de actualiza cada 100ms } // Fin del bucle infinito } // Fin de la funci贸n principal

62


Ejemplo#33: Se desea que en una pantalla LCD aparezca el estado lógico de 4 entradas conectadas en el puerto c del microcontrolador, además debe aparecer el equivalente decimal de la combinación binaria de 4 bits. Componentes ISIS: PIC16F877A, BUTTON, 5 RES(10K), CAP (22pf), CRYSTAL, LM016L(LCD 16x2), POT-HG(10K), DIPSW_4.

Figura 14-4.

// Program_13_Dip_Switch_LCD #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) #use fast_io(c) #define use_portb_lcd TRUE #include <LCD.c> #define Switch0 pin_c0 // El Pin C0 se llamará Switch0 #define Switch1 pin_c1 // El Pin C1 se llamará Switch1 #define Switch2 pin_c2 // El Pin C2 se llamará Switch2 #define Switch3 pin_c3 // El Pin C3 se llamará Switch3

63


int1 bit3,bit2,bit1,bit0; int Valor; void main(){ set_tris_b(0b00000000); set_tris_c(0b00001111); // c0 c1 c2 c3 serán entradas output_b(0x00); lcd_init(); delay_us(50); while(1){ bit0 = input(Switch0);//El estado del pin C0 se guarda en bit0 bit1 = input(Switch1);//El estado del pin C1 se guarda en bit1 bit2 = input(Switch2);// El estado del pin C2 se guarda en bit2 bit3 = input(Switch3);//El estado del pin C3 se guarda en bit3 Valor = bit3*8 + bit2*4 + bit1*2 + bit0*1; // Valor en decimal printf(lcd_putc,"\fSwitch: printf(lcd_putc,"%u %u %u %u

Valor:\n"); DEC:%u",bit3,bit2,bit1,bit0,Valor);

delay_ms(100); } // Fin del Bucle infinito } // Fin de la función principal

En este programa se han definido 4 variables: int1 bit3,bit2,bit1,bit0; int Valor; bit3: Guardará el valor del interruptor en el pin C3. bit2: Guardará el valor del interruptor en el pin C2. bit1: Guardará el valor del interruptor en el pin C1. bit0: Guardará el valor del interruptor en el pin C0.

64


La variable entera Valor será la responsable de almacenar la combinación binaria de 4 bits. En este caso haciendo uso de la representación binaria sabemos q un número de 4 bits se expresa de la siguiente manera: Supongamos la combinación binaria 1010(b). 23

22

21 2 0

1

0

1

0

El valor decimal correspondiente será: Valor = (1)23 + (0)22 + (1)21 + (0)20 Valor = (1) x 8 + (0) x 4 + (1) x 2 +(0) x 1 Valor = 10 Esta es la razón por la cual, la variable Valor se escribe de forma general como: Valor = bit3*8 + bit2*4 + bit1*2 + bit0*1; Finalmente luego de tener los valores de cada una de las entradas y el valor decimal que representa la combinación de 4 bits, se muestran en la pantalla LCD. Como vimos anteriormente hacemos uso de la función: printf(fname,cstring,values) fname: En este caso, es la función lcd_putc que se utiliza para mostrar cadenas de caracteres en la pantalla LCD. cstring: Está representada por "%u %u %u %u aparecerá una variable entera sin signo.

DEC:%u", donde %u indica que

values: Son cada una de las variables que aparecerán en su lugar correspondiente donde se ha indicado que aparecerá un entero sin signo (%u). Las variables, bit3, bit2, bit1, bit0, Valor (Deben estar separadas por comas). Aparecerán en el mismo orden en la pantalla LCD. printf(lcd_putc,"%u %u %u %u

DEC:%u",bit3,bit2,bit1,bit0,Valor);

65


PROYECTOS PROPUESTOS CON EL MODULO LCD 1) En una pantalla LCD haga que aparezca su nombre por 3 segundos, luego limpie la pantalla para mostrar su edad y fecha de nacimiento por 2.5 segundos, finalmente, debe aparecer su correo electrónico y número de teléfono por 4 segundos, recuerde que la pantalla LCD solo acepta 16 caracteres por línea. El proceso debe repetirse indefinidamente. 2) En una pantalla LCD mostrar el estado de 8 entradas conectadas en un puerto además de su respectivo valor decimal.

66


15.- MANEJO DE MOTORES PASO A PASO Los motores paso a paso son muy utilizados en mecanismos donde es muy importante la precisión del movimiento, como en robótica, en proyectos aeroespaciales, en maquinarias industriales, como fresadoras, tornos, inclusive, los encontramos en las computadoras, en periféricos como las impresoras, CD-ROM, Flopy disk, etc. La Diferencia con los motores paso a paso de corriente continua y corriente alterna, se encuentra en q los motores paso a paso son más precisos, en su velocidad, movimiento, y giros, ya que trabajan con señales digitales, es decir 0L y 1L. Existen dos tipos de motores paso a paso, los bipolares y los unipolares, la diferencia entre ellos es que los motores bipolares poseen 2 bobinas y son de 4 hilos, en cambio, los motores unipolares, poseen un mayor número de bobinas facilitando el manejo al usuario y poseen desde 5 hasta 8 hilos, para su manejo. Para manejar el motor paso a paso bipolar se requiere de un circuito integrado L298 que dispone de 2 puentes H, o por lo menos de menos debemos hacer un arreglo de 8 transistores (4 NPN y 4PNP). También se necesita el circuito integrado L297. En cuanto al Voltaje de alimentación que se requiere para los 2 tipos de motores, varían, entre 1.3V, 1.9V, 4.5V, 5V, 12V y 24V, y la corriente que consume el motor puede estar desde 300mA hasta 3ª, dependiendo del torque que este se encuentre manejando.

Figura 15-1.

67


En las especificaciones del motor viene dado el grado de precisión, entre algunos de ellos tenemos de 0.72°, 1.8°, 3.75°,7.5°, 15°, 90°, etc. Y De acuerdo al grado que gire el motor por impulso, se debe dar un número de pasos para que este gire una vuelta completa. Por ejemplo: Para un motor que el grado de giro sea 90°, el numero de pasos que debe dar, para una vuelta completa será de 4, para un motor de 1.8° el numero de pasos será de 200. 15.1.- PRINCIPIO DE FUNCIONAMIENTO Básicamente estos motores están constituidos normalmente por un rotor sobre el que van aplicados distintos imanes permanentes y por un cierto número de bobinas excitadoras bobinadas en su estator. Las bobinas son parte del estator y el rotor es un imán permanente. Toda la conmutación (o excitación de las bobinas) deber ser externamente manejada por un controlador. 15.2.- SECUENCIAS PARA MANEJAR MOTORES PASO A PASO UNIPOLARES Existen 2 secuencias posibles para este tipo de motores, las cuales se detallan a continuación. Todas las secuencias comienzan nuevamente por el paso 1 una vez alcanzado el paso final. Para revertir el sentido de giro, simplemente se deben ejecutar las secuencias en modo inverso. 15.2.1.- MANEJO DE MOTORES PASO A PASO MEDIANTE LA SECUENCIA WAVE DRIVE Wave drive o secuencia por ola es la forma más fácil de manejar un motor, consiste en energizar, una sola bobina a la vez A,B,C y por ultimo D, a continuación veremos la tabla de energizado para conseguir que el motor gire en ambos sentidos.

Figura 15-2. Tabla de energizado en secuencia Wave Drive anti horario.

68


Figura 15-3. Tabla de energizado en secuencia Wave Drive horario.

Ejemplo#34: Generar una secuencia wave drive para controlar un motor paso a paso unipolar, para manejar este tipo de motor es necesario utilizar un buffer integrado ULN2003 para motores de baja corriente (500ma) y un voltaje máximo de 50VDC. El estado de las bobinas debe ser visualizado con leds. En la pantalla LCD debe aparecer la cantidad de pasos realizados por el motor y el tipo de secuencia que realiza. NOTA: El motor utilizado en la simulación es de 90° grados por paso, es decir cada 4 pasos este dará una vuelta completa. Componentes ISIS: PIC16F877A, BUTTON, RES(10K), 4 RES(220), CAP (22pf), CRYSTAL, LM016L(LCD 16x2), POT-HG(10K), ULN2003A, MOTORSTEPPER, BATTERY.

Figura 15-5.

69


// Program_14_Wave_Drive #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) #use fast_io(c) #use fast_io(d) #define use_portb_lcd TRUE #include <LCD.c> int i,Tiempo = 100; int16 Pasos; // Variable que guardara la cantidad de pasos void Actualiza_LCD(); // Funcion para actualizar la LCD void main(){ set_tris_b(0b00000000); set_tris_c(0b00000000); set_tris_d(0b00000000); output_b(0x00); output_c(0x00); output_d(0x00); lcd_init(); delay_us(50); printf(lcd_putc,"

M.Paso a Paso\n");

printf(lcd_putc,"

Wave Drive");

delay_ms(2000); while(1){ for(i=1;i<=4;i++){ output_d(0b00000001); // Bobina A = ON 70


output_c(0b00000001); Actualiza_LCD(); delay_ms(Tiempo); output_d(0b00000010); // Bobina B = ON output_c(0b00000010); Actualiza_LCD(); delay_ms(Tiempo); output_d(0b00000100); // Bobina C = ON output_c(0b00000100); Actualiza_LCD(); delay_ms(Tiempo); output_d(0b00001000); // Bobina D = ON output_c(0b00001000); Actualiza_LCD(); delay_ms(Tiempo); } // Fin del bucle For } // Fin del bucle infinito } // Fin de la funci贸n principal void Actualiza_LCD(){ Pasos++; printf(lcd_putc,"\f printf(lcd_putc,"

Wave Drive\n"); Pasos:%lu

}

71

",Pasos);


Ejemplo#35: Generar una secuencia wave drive con cambio de giro, es decir cada vez que el motor de 90° por paso de una vuelta, hacer que este empiece a girar en sentido contrario. El estado de las bobinas debe ser visualizado con leds. En la pantalla LCD debe aparecer el sentido de giro del motor. Se usará el esquema electrónico de la Figura 15-5. // Program_15_Motor_PP_Cambio_de_Giro #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) #use fast_io(c) #use fast_io(d) #define use_portb_lcd TRUE #include <LCD.c> int i,Tiempo = 100; void Actualiza_LCD_H(); // Indica en pantalla el sentido Horario void Actualiza_LCD_A();//Indica en pantalla el sentido Anti-H void main(){ set_tris_b(0b00000000); set_tris_c(0b00000000); set_tris_d(0b00000000); output_b(0x00); output_c(0x00); output_d(0x00); lcd_init(); delay_us(50); printf(lcd_putc,"

M.Paso a Paso\n");

printf(lcd_putc,"

Wave Drive");

delay_ms(2000); 72


while(1){ for(i=1;i<=4;i++){ output_d(0b00000001); // Bobina A = ON output_c(0b00000001); Actualiza_LCD_H(); delay_ms(Tiempo); output_d(0b00000010); // Bobina B = ON output_c(0b00000010); Actualiza_LCD_H(); delay_ms(Tiempo); output_d(0b00000100); // Bobina C = ON output_c(0b00000100); Actualiza_LCD_H(); delay_ms(Tiempo); output_d(0b00001000); // Bobina D = ON output_c(0b00001000); Actualiza_LCD_H(); delay_ms(Tiempo); } // Fin del bucle For for(i=1;i<=4;i++){ output_d(0b00001000); // Bobina D = ON output_c(0b00001000); Actualiza_LCD_A(); delay_ms(Tiempo); output_d(0b00000100); // Bobina C = ON output_c(0b00000100); Actualiza_LCD_A(); 73


delay_ms(Tiempo); output_d(0b00000010); // Bobina B = ON output_c(0b00000010); Actualiza_LCD_A(); delay_ms(Tiempo); output_d(0b00000001); // Bobina A = ON output_c(0b00000001); Actualiza_LCD_A(); delay_ms(Tiempo); } // Fin del bucle For } // Fin del bucle infinito } // Fin de la funci贸n principal void Actualiza_LCD_H(){ printf(lcd_putc,"\f printf(lcd_putc,"

Wave Drive

\n");

Giro: Horario");

} void Actualiza_LCD_A(){ printf(lcd_putc,"\f

Wave Drive\n");

printf(lcd_putc,"Giro:Antihorario"); }

74


15.2.2.- MANEJO DE MOTORES PASO A PASO MEDIANTE LA SECUENCIA FULL STEP También conocida como secuencia por paso completo, esta es la manera que recomiendan los fabricantes, debido a que siempre se encuentran energizadas 2 bobinas, se obtiene un alto torque de paso y de retención, y consume el 40% más de corriente que el caso anterior.

Figura 15-6. Tabla de energizado de bobinas en la secuencia de paso completo.

Ejemplo#36: Generar una secuencia Full Step para controlar un motor paso a paso unipolar. El estado de las bobinas debe ser visualizado con leds. En la pantalla LCD debe aparecer la cantidad de pasos realizados por el motor y el tipo de secuencia que realiza. Se usará el esquema electrónico de la Figura 15-5. // Program_16_Motor_PP_Full_Step #include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) #use fast_io(c) #use fast_io(d) #define use_portb_lcd TRUE #include <LCD.c> int i,Tiempo = 100; int16 Pasos; void Actualiza_LCD(); void main(){ set_tris_b(0b00000000); set_tris_c(0b00000000); set_tris_d(0b00000000); 75


output_b(0x00); output_c(0x00); output_d(0x00); lcd_init(); delay_us(50); printf(lcd_putc," printf(lcd_putc,"

M.Paso a Paso\n"); Full Step");

delay_ms(2000); while(1){ for(i=1;i<=4;i++){ output_d(0b00001001); // A y D ON output_c(0b00001001); Actualiza_LCD(); delay_ms(Tiempo); output_d(0b00000011); // A y B ON output_c(0b00000011); Actualiza_LCD(); delay_ms(Tiempo); output_d(0b00000110); // B y C ON output_c(0b00000110); Actualiza_LCD(); delay_ms(Tiempo); output_d(0b00001100); // C y D ON output_c(0b00001100); Actualiza_LCD(); delay_ms(Tiempo); } // Fin del bucle For 76


} // Fin del bucle infinito } // Fin de la funci贸n principal void Actualiza_LCD(){ Pasos++; printf(lcd_putc,"\f printf(lcd_putc,"

Full Step Pasos:%lu

}

77

\n");

",Pasos);


15.3.- SECUENCIAS PARA MANEJAR MOTORES PASO A PASO BIPOLARES Estos motores necesitan la inversiรณn de la corriente que circula en sus bobinas en una secuencia determinada. Cada inversiรณn de la polaridad provoca el movimiento del eje en un paso, cuyo sentido de giro estรก determinado por la secuencia seguida. A continuaciรณn se puede ver la tabla con la secuencia necesaria para controlar motores paso a paso del tipo Bipolares: Paso 1 2 3 4

Terminales A +V +V -V -V

B -V -V +V +V

C +V -V -V +V

D -V +V +V -V

Ejemplo#37: Generar una secuencia Full Step para el control de un motor paso a paso bipolar, se usarรกn los circuitos integrados L297 y L298, estos permiten generar las secuencias necesarias para energizar las bobinas del motor bipolar. En la pantalla LCD debe aparecer el tipo de secuencia. Componentes ISIS: PIC16F877A, BUTTON, RES(10K), 2 RES(1K) 4 RES(220), CAP (22pf), CRYSTAL, LM016L(LCD 16x2), POT-HG(10K), L297, L298, MOTORSTEPPER, BATTERY.

Figura 15-6.

78


#include <16f877A.h> #fuses xt,nowdt #use delay (clock=4000000) #use fast_io(b) #use fast_io(d) #define use_portb_lcd TRUE #include <LCD.c> int16 Pasos; void main(){ set_tris_b(0b00000000); set_tris_d(0b00000000); output_b(0x00); output_d(0x00); lcd_init(); delay_us(50); printf(lcd_putc,"

M.Paso a Paso\n");

printf(lcd_putc,"

LM297 y LM298");

delay_ms(2000); while(1){ output_d(0b000000001); // Indica Full Step y sentido A.H printf(lcd_putc,"\f printf(lcd_putc,"

Secuencia

\n");

Full Step");

delay_ms(100); } // Fin del bucle infinito } // Fin de la funci贸n proncipal En el pin 19 (Half/Full) del L297 si se coloca un 1 se puede elegir el modo Wave Drive o medio paso, con un 0 se elige el modo Full Step. El pin 20 (CW/CCW) permite elegir el sentido de giro con 1 elegimos el sentido horario y con 0 el sentido anti horario. 79


PROYECTOS PROPUESTOS CON MOTORES PASO A PASO 1) Al esquema de la Figura 15-5 agregar 1 pulsador en el puerto A que permita elegir el sentido de giro del motor. Inicialmente el motor debe estar detenido, cuando se presione el bot贸n, el motor debe empezar a girar en sentido horario, si se presiona de nuevo este debe girar en sentido contrario. En la pantalla debe aparecer el sentido de giro. El estado de las bobinas debe ser representado con leds. 2) Generar una secuencia de cambio de giro con un motor bipolar, cuando el motor de una vuelta completa, se debe invertir el giro del motor, usar el esquema de la Figura 15-6.

80


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.