Descarga la aplicación para disfrutar aún más
Vista previa del material en texto
SISTEMA DE MICROPROCESADORES GERMÁN JESÚS PEREIRA MUÑOZ, Ph. D. (c) LA PAZ, BOLIVIA 2021 PROPIEDAD INTELECTUAL DEL AUTOR Ing. Germán Jesús Pereira Muñoz Página 2 1. Introducción. Un micro controlador es una microcomputadora ya que posee las mismas características que son la unidad de procesamiento, la memoria y los dispositivos de entrada salida principalmente. Este dispositivo es programable, es decir, posee una memoria de programa donde contiene una secuencia de instrucciones aritmético lógicas introducidas por el programador mediante un software especializado. Son diseñados para reducir el costo económico y el consumo de energía de un sistema en particular. Por eso el tamaño de la unidad de procesamiento, la cantidad de memoria y los periféricos incluidos dependerán de la aplicación. El control de un electrodoméstico sencillo como una batidora, utilizará un procesador muy pequeño (4 u 8 bit) por que sustituirá a un autómata finito. En cambio un reproductor de música y/o vídeo digital requerirá de un procesador de 32 bit o de 64 bit. Los microcontroladores representan la inmensa mayoría de los chips de computadoras vendidos, sobre un 50% son controladores "simples" y el restante corresponde a DSPs más especializados. Mientras se pueden tener uno o dos microprocesadores de propósito general en casa (vd. está usando uno para esto), usted tiene distribuidos seguramente entre los electrodomésticos de su hogar una o dos docenas de microcontroladores. Pueden encontrarse en casi cualquier dispositivo electrónico como automóviles, lavadoras, hornos, teléfonos, etc. 2. Microcontroladores AVR. Los AVR son una familia de microcontroladores RISC deAtmel. La arquitectura de los AVR fue concebida por dos estudiantes en el Norwegian Institute of Technology, y posteriormente refinada y desarrollada en Atmel Norway, la empresa subsidiaria deAtmel, fundada por los dos arquitectos del chip. El AVR es una CPU de arquitectura Harvard tiene 32 registros de 8 bits. Algunas instrucciones sólo operan en un subconjunto de estos registros. La concatenación de los 32 registros, los registros de entrada/salida y la memoria de datos conforman un espacio de direcciones unificado, al cual se accede a través de operaciones de carga/almacenamiento. El AVR fue diseñado desde un comienzo para la ejecución eficiente de código C compilado. Como este lenguaje utiliza profusamente punteros para el manejo de variables en memoria, los tres últimos pares de registros internos del procesador, son usados como punteros de 16 bit al espacio de memoria externa, bajo los nombres X, Y y Z. Esto es un compromiso que se hace en arquitecturas de ocho bit desde los tiempos de Intel 8008, ya que su tamaño de palabra nativo de 8 bit (256 localidades accedidas) es pobre para direccionar. Por otro lado, hacer que todo el banco superior de 16 registros de 8 bit tenga un comportamiento alterno como un banco de 8 registros de 16 bit, complicaría mucho el diseño, violando la premisa original de su simplicidad. Ing. Germán Jesús Pereira Muñoz Página 3 El set de instrucciones AVR está implementado físicamente y disponible en el mercado en diferentes dispositivos, que comparten el mismo núcleo AVR pero tienen distintos periféricos y cantidades de RAM y ROM: desde el microcontrolador de la familia Tiny AVR ATtiny11 con 1KB de memoria flash y sin RAM (sólo los 32 registros), y 8 pines, hasta el microcontrolador de la famila Mega AVRATmega2560 con 256KB de memoria flash, 8KB de memoria RAM, 4KB de memoria EEPROM, conversor análogo digital de 10 bits y 16 canales, temporizadores, comparador analógico, JTAG, etc. La compatibilidad entre los distintos modelos es preservada en un grado razonable. Los microcontroladores AVR tienen una cañería ('pipeline' en inglés) con dos etapas (cargar y ejecutar), que les permite ejecutar la mayoría en un ciclo de reloj, lo que los hace relativamente rápidos entre los microcontroladores de 8-bit. 3. Arquitectura de un microcontrolador AVR. La arquitectura de estos controladores: Ing. Germán Jesús Pereira Muñoz Página 4 A manera de maximizar el desempeño y el paralelismo, tenemos que la arquitectura Harvard separa memoria y buses para programa y para datos. Las instrucciones se ejecutan seguidamente, mientras una se ejecuta, la siguiente ya ha sido rescatada de la memoria de programa y colocada a la pila. En la grafica puede notarse el núcleo del procesamiento que es la ALU, las memorias de datos (RAM y ROM), la memoria de programa (Flash), el contador de programa, las líneas de entrada y salida y finalmente todos los módulos que se anexan al bus de datos que son las características de cada microcontrolador. 4. Software de programación (WIN-AVR). Uno de las decenas de software que existen para manejar este tipo de microcontroladores que resulta muy sencillo manejar es el WIN-AVR. Este software se diseñó para trabajar con lenguaje C y realiza la compilación de los programas de manera rápida y además puede programar físicamente el microcontrolador de la misma manera. La compatibilidad que tiene con los grabadores también es bastante amplia. Este programa muestra un ‘notepad’ como el siguiente donde se puede programar en el lenguaje C por ejemplo las secuencias que deba seguir el microcontrolador. Cuando se abre el programa, se muestra el espacio de trabajo al centro, los proyectos a la izquierda y el estado de la compilación o programación abajo. Ing. Germán Jesús Pereira Muñoz Página 5 -Creación de un proyecto sobre el cual trabajar. Para crear un proyecto, simplemente se debe seleccionar ‘nuevo proyecto’ en la ventana ‘file’. Luego se da el nombre al proyecto y donde quiera guardárselo. Una vez creado, debería aparecer en la parte izq. de la ventana del notepad. Ing. Germán Jesús Pereira Muñoz Página 6 Luego, guardamos el espacio de trabajo que por defecto aparece como ‘new’. Vamos a ‘file’, ‘save as’ y guadamos en una carpeta, preferentemente donde hayamos guardado el proyecto, con la extensión .c. Finalmente añadimos este espacio de trabajo al proyecto, para que cada vez que se abra el proyecto, directamente nos muestre este espacio de trabajo y todos los que tengamos añadidos. Ing. Germán Jesús Pereira Muñoz Página 7 5. Grabadores del AVR. Existen varios grabadores para este microcontrolador de entre los cuales los más comunes son: Grabador por puerto paralelo: Ing. Germán Jesús Pereira Muñoz Página 8 Este grabador es sencillo de armar y funciona correctamente con el WIN-AVR. Solo debe conectarse a los pines de Reset, MISO, MOSI y SCK tal como se muestra en la figura. La única dificultad que se tiene con este grabador es que solo las maquinas antiguas Pentium 3 poseen un puerto paralelo y no con todos los sistemas operativos es posible utilizarlo. Grabador serial: Este grabador de la misma manera que el anterior es bastante fácil de armar y solo debe conectarse al microcontrolador con los pines indicados. Un puerto serial es mucho más común que un puerto serial ya que muchas maquinas de escritorio lo poseen. Funciona bien con el WIN-AVR y es bastante practico ya por su tamaño. Grabador USB: Ing. Germán Jesús Pereira Muñoz Página 9 Este grabador necesita de un microcontrolador maestro que graba a los demás y eso lo hace más caro y menos atractivo, pero la gran ventaja es que graba a otros microcontroladores por puerto USB que tienen casi todas las maquinas actuales, sobre todo las laptop. Pero existe una variación que es mucho más aplicativa para fines académicos como entrenador y se trata de una versión de boot loader.Esta versión solo requiere un microcontrolador (con el que se quiere trabajar) para grabarle un programa con cualquier grabador anterior; el programa comunica al microcontrolador con el puerto usb de una PC y puede grabarse cualquier programa diferente ya que lo reconoce como un dispositivo externo. El programa a grabarse no borrara al boot loader ya que este graba este programa después de él mismo en la memoria. Lo único deficiente de este grabador es que solo se graba a sí mismo y no a otros microcontroladores pero lo hace atractivo para fines académicos. El loader: Ing. Germán Jesús Pereira Muñoz Página 10 6. Programación. Para programar en el microcontrolador AVR no solo se necesita conocer el lenguaje C o el lenguaje ensamblador sino también los registros internos que posee este chip. Para acceder a las distintas funciones de este controlador, este consta de registros los cuales deben ser configurados por programa para un funcionamiento deseado. Veremos las herramientas que tiene analizando algunos ejemplos de programas en base a lenguaje C. 6.1. Uso de instrucciones básicas de entrada / salida en displays. Los componentes más sencillos a controlar son los leds ya que solo requieren una salida digital de 5V del microcontrolador. Entonces para familiarizarnos con el manejo de instrucciones de salida, una aplicación bastante sencilla es el despliegue en displays de 7 segmentos que son arreglos de leds de manera de desplegar números o letras. Ing. Germán Jesús Pereira Muñoz Página 11 El programa más sencillo a realizar es el de un contador ascendente descendente en el que tenemos instrucciones de entrada e instrucciones de salida: #include<avr/io.h> #include<util/delay.h> #define asce (PINC&1) //Hasta aqui definimos las librerias a utilizar donde avr/io contiene los registros del avr, útil/delay permite el uso de retardos en procesamiento y #define que permite la definición de variables. int a; a=0; void delay_ms(int n){ while(n--) _delay_ms(1); } //Una sencilla rutina de retardo. void ascendente(void){ switch(a){ case 0: PORTB=0xFE; a=a+1; break; case 1: PORTB=0xB0; a=a+1; break; case 2: PORTB=0xED; a=a+1; break; case 3: PORTB=0xF9; a=a+1; break; case 4: PORTB=0xB3; a=a+1; break; case 5: PORTB=0xDB; a=a+1; break; case 6: PORTB=0xDF; a=a+1; break; case 7: PORTB=0xF0; a=a+1; break; case 8: PORTB=0xFF; a=a+1; break; case 9: PORTB=0xF3; a=0; break; } } void descendente(void){ switch(a){ case 0: PORTB=0xFE; a=9; break; case 1: Ing. Germán Jesús Pereira Muñoz Página 12 PORTB=0xB0; a=a-1; break; case 2: PORTB=0xED; a=a-1; break; case 3: PORTB=0xF9; a=a-1; break; case 4: PORTB=0xB3; a=a-1; break; case 5: PORTB=0xDB; a=a-1; break; case 6: PORTB=0xDF; a=a-1; break; case 7: PORTB=0xF0; a=a-1; break; case 8: PORTB=0xFF; a=a-1; break; case 9: PORTB=0xF3; a=a-1; break; } } //Para cada caso ascendente o descendente tenemos las instrucciones de salida correspondientes a un número. int main (void){ DDRB=0xFF; DDRC=0; while(1){ if (asce==0) descendente(); else ascendente(); delay_ms(500); } } //La rutina principal pregunta un PIN del controlador y en base a este decide si el contador será ascendente o descendente y para visualizarlo tenemos un retardo de 500 ms. Lo que realiza el programa es un conteo interno del 0 al 9 que se guarda en una variable ‘a’ y preguntamos con el switch que numero tiene ‘a’ y en base a esto sacamos un dato para el display de 7 segmentos y decrementamos o incrementamos ‘a’ de acuerdo al funcionamiento del contador. Nótese que para entradas de datos definimos el registro DDRC del puerto C con 0 y para salidas DDRB del puerto B con ff. Ing. Germán Jesús Pereira Muñoz Página 13 PB0/T0/XCK 1 PB1/T1 2 PB2/AIN0/INT2 3 PB3/AIN1/OC0 4 PB4/SS 5 PB5/MOSI 6 PB6/MISO 7 PB7/SCK 8 RESET 9 XTAL2 12 XTAL1 13 PD0/RXD 14 PD1/TXD 15 PD2/INT0 16 PD3/INT1 17 PD4/OC1B 18 PD5/OC1A 19 PD6/ICP1 20 PD7/OC2 21 PC0/SCL 22 PC1/SDA 23 PC2/TCK 24 PC3/TMS 25 PC4/TDO 26 PC5/TDI 27 PC6/TOSC1 28 PC7/TOSC2 29 PA7/ADC7 33 PA6/ADC6 34 PA5/ADC5 35 PA4/ADC4 36 PA3/ADC3 37 PA2/ADC2 38 PA1/ADC1 39 PA0/ADC0 40 AREF 32 AVCC 30 U1 ATMEGA32 R1 10k 6.2. Aplicación básica. (Semáforo) Otra aplicación sencilla de este controlador es un semáforo. Este tipo de control es secuencial ya que el semáforo sigue una secuencia sencilla que es rojo, verde, amarillo un tiempo corto y luego rojo nuevamente. La aplicación será para un par de ellos: #include<avr/io.h> #include<util/delay.h> unsigned char estado; unsigned char s,c; unsigned char previo; void delay_ms(int n){ while(n--) _delay_ms(1); } #define R1 PA0 #define A1 PA1 #define V1 PA2 #define R2 PA3 #define A2 PA4 #define V2 PA5 #define AA 0 #define RV 1 #define RA 2 #define VR 3 #define AR 4 #define cambio (PINC&1) #define emergencia (PINC&2) void cambiar(char nuevo){ estado=nuevo; c=0; s=0; } void condiciones(void){ switch(estado){ case AA: if(cambio==0) cambiar(RV); break; case RV: if ((cambio==0)||(s==10)){ s=0; Ing. Germán Jesús Pereira Muñoz Página 14 cambiar(RA);} if(emergencia==0) cambiar(AA); break; case RA: if(s==2){ s=0; cambiar(VR);} if (emergencia==0) cambiar(AA); break; case VR: if((cambio==0)||(s==10)){ s=0; cambiar(AR);} if (emergencia==0) cambiar(AA); break; case AR: if(s==2){ s=0; cambiar(RV);} if (emergencia==0) cambiar(AA); break; } } void ejecutar(void){ switch(estado){ case RV: PORTA=(1<<R1)|(1<<V2); break; case RA: PORTA=(1<<R1)|(1<<A2); break; case VR: PORTA=(1<<V1)|(1<<R2); break; case AR: PORTA=(1<<A1)|(1<<R2); break; case AA: if(c<50) PORTA=(1<<A1)|(1<<A2); else PORTA=0; } if (++c>100){c=0; s++;} delay_ms(10);} int main(void){ DDRA=0xFF; DDRC=0; cambiar(AA); while(1){ condiciones(); ejecutar(); } } El programa tiene las variables estado, s, c y las entradas externas de ‘emergencia’ y ‘cambio’ Lo primero que tenemos para los semáforos es el ‘stand by’ cuando no fucnionan, sino que despliegan luces amarillas intermitentes. Entonces nos definimos los estados en los que estarán los semáforos, por ejemplo, AA para amarillo-amarillo, RV para un Ing. Germán Jesús Pereira Muñoz Página 15 semáforo en rojo y el otro en verde, etc. Las variables ‘s’ y ‘c’ solo se utilizan para la intermitencia del estado AA. Entonces los semáforos se mantienen en amarillo intermitente hasta que alguien presiona cambio que cambia los semáforos a RV y desde allí el lenguaje C condiciona las acciones a seguir para la secuencia que se repite una y otra vez. Si alguien presiona ‘emergencia’ los semáforos van de nuevo a AA. Nótese la instrucción para poner a uno lógico bits que es: PORTA=(1<<A1)|(1<<A2); PB0/T0/XCK 1 PB1/T1 2 PB2/AIN0/INT2 3 PB3/AIN1/OC0 4 PB4/SS 5 PB5/MOSI 6 PB6/MISO 7 PB7/SCK 8 RESET 9 XTAL2 13 XTAL1 12 PD0/RXD 14 PD1/TXD 15 PD2/INT0 16 PD3/INT1 17 PD4/OC1B 18 PD5/OC1A 19 PD6/ICP1 20 PD7/OC2 21 PC0/SCL 22 PC1/SDA 23 PC2/TCK 24 PC3/TMS 25 PC4/TDO 26 PC5/TDI 27 PC6/TOSC1 28 PC7/TOSC2 29 PA7/ADC7 33 PA6/ADC6 34 PA5/ADC5 35 PA4/ADC4 36 PA3/ADC3 37 PA2/ADC2 38 PA1/ADC1 39 PA0/ADC0 40 AREF 32 AVCC 30 U1 ATMEGA32 D1 LED-YELLOW D2 LED-YELLOW D3 LED-RED D4 LED-RED D5 LED-GREEN D6 LED-GREEN R1 100R R2 100R 6.3. Multiplexación de displays. Otro ejemplo, el más sencillo de todos, es la multiplexacion de displays. Esto consiste en la utilización de varios displays con un común bus de datos y se habilitan cada uno mediante otro puerto que será el de control.En nuestro caso el bus de datos esta dado por el puerto B y el de control por el puerto C. Como es bastante sencillo, solo se necesitan botar datos por puerto o jalarlos desde una tabla, pero con instrucciones de salida basta. Cada dato a sacar representa una letra. #include<avr/io.h> #include<util/delay.h> void mostrar (void){ PORTC=0xFE; PORTB=0x6E; PORTB=0x00; PORTC=0xFD; PORTB=0xFC; PORTB=0x00; PORTC=0xFB; PORTB=0x1C; PORTB=0x00; PORTC=0x07; PORTB=0xEE; PORTB=0x00; } int main (void){ DDRB=0xff; DDRC=0xff; while(1){ mostrar(); } } Nótese que puede escribirse cualquier cosa y con cuantos displays se quiera. Ing. Germán Jesús Pereira Muñoz Página 16 PB0/T0/XCK 1 PB1/T1 2 PB2/AIN0/INT2 3 PB3/AIN1/OC0 4 PB4/SS 5 PB5/MOSI 6 PB6/MISO 7 PB7/SCK 8 RESET 9 XTAL2 13 XTAL1 12 PD0/RXD 14 PD1/TXD 15 PD2/INT0 16 PD3/INT1 17 PD4/OC1B 18 PD5/OC1A 19 PD6/ICP1 20 PD7/OC2 21 PC0/SCL 22 PC1/SDA 23 PC2/TCK 24 PC3/TMS 25 PC4/TDO 26 PC5/TDI 27 PC6/TOSC1 28 PC7/TOSC2 29 PA7/ADC7 33 PA6/ADC6 34 PA5/ADC5 35 PA4/ADC4 36 PA3/ADC3 37 PA2/ADC2 38 PA1/ADC1 39 PA0/ADC0 40 AREF 32 AVCC 30 U1 ATMEGA32 6.4. Manejo de pantalla LCD. Una pantalla LCD puede desplegar de una mejor manera cualquier texto y por ello son más utilizadas. Pero, a la vez, son más complicadas de utilizar comparándolas con los simples leds de los displays. A estas pantallas se les debe enviar datos de comando y los datos de cada letra en ASCII. JUEGOS DE INSTRUCCIONES: Estas son las instrucciones para el control del modulo LCD Hitachi 44780 o compatible CLEAR DISPLAY Borra el módulo LCD y coloca el cursor en la primera posición (dirección 0). Pone el bit I/D a 1 por defecto. RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 0 0 0 0 0 0 0 0 1 Tiempo de ejecución: 1.64mS HOME Coloca el cursor en la posición de inicio (dirección 0) y hace que el display comience a desplazarse desde la posición original. El contenido de la memoria RAM de datos de visualización (DD RAM) permanece invariable. La dirección de la memoria RAM de datos para la visualización (DD RAM) es puesta a 0. RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 0 0 0 0 0 0 0 1 X Tiempo de ejecución: 1.64mS Ing. Germán Jesús Pereira Muñoz Página 17 ENTRY MODE SET Establece la dirección de movimiento del cursor y especifica si la visualización se va desplazando a la siguiente posición de la pantalla o no. Estas operaciones se ejecutan durante la lectura o escritura de la DD RAM o CG RAM. Para visualizar normalmente poner el bit S=0. RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 0 0 0 0 0 0 1 I/D S Tiempo de ejecución: 40µS DISPLAY ON/OFF CONTROL Activa o desactiva poniendo en ON/OFF tanto al display (D) como al cursor (C) y se establece si este último debe o no parpadear (B). RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 0 0 0 0 0 1 D C B Tiempo de ejecución: 40µS CURSOR OR DISPLAY SHIFT Mueve el cursor y desplaza el display sin cambiar el contenido de la memoria de datos de visualización DD RAM. RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 0 0 0 0 1 S/C R/L X X Tiempo de ejecución: 40µS FUNCTION SET Establece el tamaño de interfase con el bus de datos (DL), número de líneas del display (N) y tipo de carácter (F) RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 0 0 0 1 DL N F X X Tiempo de ejecución: 40µS SET THE CG RAM ADDRESS El módulo LCD además de tener definidos todo el conjunto de caracteres ASCII, Ing. Germán Jesús Pereira Muñoz Página 18 permite al usuario definir 4 u 8 caracteres gráficos. La composición de estos caracteres se va guardando en una memoria llamada CG RAM con capacidad para 64 bytes. Cada carácter gráfico definido por el usuario se compone de 16 u 8 bytes que se almacenan en sucesivas posiciones de la CG RAM. Mediante esta instrucción se establece la dirección de memoria CG RAM a partir de la cual se irán almacenando los bytes que definen un carácter gráfico. Ejecutando este comando todos los datos que se lean o escriban posteriormente, lo hacen desde esta memoria CG RAM. RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 0 0 1 Dirección de la CG RAM Tiempo de ejecución: 40µS SET THE DD RAM ADDRESS Los caracteres o datos que se van visualizando, se van almacenando en una memoria llamada DD RAM para de aquí pasar a la pantalla. Mediante esta instrucción se establece la dirección de la memoria DD RAM a partir de la cual se irán almacenando los datos a visualizar. Ejecutando este comando, todos los datos que se escriban o lean posteriormente lo harán desde esta memoria DD RAM. Las direcciones de la 80h a la 8Fh corresponden con los 16 caracteres del primer renglón y de la C0h a la CFh con los 16 caracteres del segundo renglón, para este modelo de LCD. RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 0 1 Dirección de la DD RAM Tiempo de ejecución: 40µS READ BUSY FLAG & ADDRESS Cuando el modulo LCD esta ejecutando cualquiera de estas instrucciones, tarda un cierto tiempo de ejecución en el que no se debe mandar ninguna instrucción. Para ello dispone de un flag llamado BUSY (ocupado) que indica que se está ejecutando una instrucción previa. Esta instrucción de lectura informaciónrma del estado de dicho flag además de proporcionar el valor del contador de direcciones de la CG RAM o de la DD RAM según la última que se haya empleado. RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 1 BF Dirección de la CG RAM o DD RAM Ing. Germán Jesús Pereira Muñoz Página 19 Tiempo de ejecución: 40µS WRITE DATA TO GG OR DD RAM Mediante este comando se escribe en la memoria DD RAM los datos que se quieren presentar en pantalla y que serán los diferentes códigos ASCII de los caracteres a visualizar. Igualmente se escribe en la memoria CG RAM los diferentes bytes que permiten confeccionar caracteres gráficos a gusto del usuario. El escribir en uno u otro tipo de memoria depende de si se ha empleado previamente la instrucción de direccionamiento DD RAM o la de direccionamiento CG RAM. RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 1 0 Código ASCII o byte del carácter gráfico Tiempo de ejecución: 40µS READ DATA FROM CG RAM OR DD RAM Mediante este comando se lee de la memoria DD RAM los datos que haya almacenados y que serán los códigos ASCII de los caracteres almacenados. Igualmente se lee de la memoria CG RAM los diferentes bytes con los que se ha confeccionado un determinado carácter gráfico. El leer de uno u otro tipo de memoria depende de si se ha empleado previamente la instrucción de direccionamiento de la DD RAM o la de direccionamiento CG RAM. RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 1 1 Código ASCII o byte del carácter gráfico Tiempo de ejecución: 40µS Entonces, conociendo nuestra pantalla, el programa lcd.c que será nuestro modelo para trabajar como un subprograma: #include<avr/io.h> #include<util/delay.h> #include"Lcd.h" #include<stdio.h> int Lcd_out(char c, FILE *f){ if((c==10)||(c==13)) Lcd_cmd(0xC0); else Lcd_char(c); return 0; } FILE lcd=FDEV_SETUP_STREAM( Lcd_out,NULL,_FDEV_SETUP_WRITE); Ing. Germán Jesús Pereira Muñoz Página 20 void delay_ms(long n){ while(n--) _delay_ms(1); } void Lcd_gen(char dato,char rs){ LCD_PORT=(dato&0xF0)>>(4-D4); if(rs==1) LCD_PORT|=(1<<RS); LCD_PORT|=(1<<EN); _delay_us(1); LCD_PORT&=~(1<<EN); LCD_PORT=(dato&0x0f)<<(D4); if(rs) LCD_PORT|=(1<<RS); LCD_PORT|=(1<<EN); _delay_us(1); LCD_PORT&=~(1<<EN); _delay_us(40); } void Lcd_clear(void){ Lcd_cmd(1); _delay_ms(2); } void Lcd_init(void){ LCD_PORT=(1<<RS)|(1<<EN)|(0x0f<<D4); LCD_DDR=(1<<RS)|(1<<EN)|(15<<D4); _delay_ms(5); LCD_PORT=(0x30)>>(4-D4); LCD_PORT|=(1<<EN); _delay_ms(1); LCD_PORT&=~(1<<EN); _delay_ms(5); LCD_PORT=(0x30)>>(4-D4); LCD_PORT|=(1<<EN); _delay_ms(1); LCD_PORT&=~(1<<EN); _delay_ms(5); LCD_PORT=(0x30)>>(4-D4);LCD_PORT|=(1<<EN); _delay_ms(1); LCD_PORT&=~(1<<EN); _delay_ms(5); LCD_PORT=(0x20)>>(4-D4); LCD_PORT|=(1<<EN); _delay_us(1); LCD_PORT&=~(1<<EN); _delay_ms(5); Lcd_cmd(0x28); Lcd_cmd(0x0C); Lcd_clear(); stdout=&lcd; } Lo que realiza esta parte de programa es inicializar la lcd con comandos correspondientes y habilita la función para escribir datos a la pantalla. El nexo de este programa es Lcd.h: #define LCD_PORT PORTB #define LCD_DDR DDRB #define RS PB0 #define EN PB1 #define D4 PB2 #define D5 PB3 #define D6 PB4 #define D7 PB5 Ing. Germán Jesús Pereira Muñoz Página 21 void Lcd_gen(char dato,char rs); #define Lcd_cmd(c) Lcd_gen(c,0) #define Lcd_char(d) Lcd_gen(d,1) void Lcd_clear(void); void delay_ms(long n); void Lcd_init(void); Aquí están las definiciones de cada pin de la lcd y los prototipos de las funciones del programa anterior, como Lcd_init para iniciar y Lcd_cmd para mandar comando. Entonces usamos los programas anteriores con un programa principal: #include<avr/io.h> #include "lcd.h" #include<stdio.h> #include<util/delay.h> int main (void){ Lcd_init(); while(1){ Lcd_cmd(0x80); printf("ETN 505"); delay_ms(200); Lcd_clear(); Lcd_cmd(0x80); printf("programacion"); delay_ms(200); Lcd_clear(); Lcd_cmd(0x80); printf("y metodos"); delay_ms(200); Lcd_clear(); Lcd_cmd(0x80); printf("numericos"); delay_ms(200); Lcd_clear(); } } El programa solo llama a las funciones definidas anteriormente para escribir un mensaje. Nótese el uso de la instrucción ‘printf ’ para escribir en la pantalla lcd, cosa que es posible solo con el stream de lcd.c. PB0/T0/XCK 1 PB1/T1 2 PB2/AIN0/INT2 3 PB3/AIN1/OC0 4 PB4/SS 5 PB5/MOSI 6 PB6/MISO 7 PB7/SCK 8 RESET 9 XTAL2 13 XTAL1 12 PD0/RXD 14 PD1/TXD 15 PD2/INT0 16 PD3/INT1 17 PD4/OC1B 18 PD5/OC1A 19 PD6/ICP1 20 PD7/OC2 21 PC0/SCL 22 PC1/SDA 23 PC2/TCK 24 PC3/TMS 25 PC4/TDO 26 PC5/TDI 27 PC6/TOSC1 28 PC7/TOSC2 29 PA7/ADC7 33 PA6/ADC6 34 PA5/ADC5 35 PA4/ADC4 36 PA3/ADC3 37 PA2/ADC2 38 PA1/ADC1 39 PA0/ADC0 40 AREF 32 AVCC 30 U1 ATMEGA32 D 7 1 4 D 6 1 3 D 5 1 2 D 4 1 1 D 3 1 0 D 2 9 D 1 8 D 0 7 E 6 R W 5 R S 4 V S S 1 V D D 2 V E E 3 LCD1 LM016L RV1 10 6.5. LCD graficadora. Ing. Germán Jesús Pereira Muñoz Página 22 De la misma manera que la lcd anterior, esta posee diferentes comandos. El modelo a utilizar es la pantalla LGM1264. El programa glc.c: #include <avr/io.h> #include <util/delay.h> #include "glcd.h" unsigned char glcd_general_read(char flags){ char temp; GLCD_CTRL&=~((1<<GLCD_CS1)|(1<<GLCD_CS2)); if(flags & _CS1) GLCD_CTRL|=(1<<GLCD_CS1); else GLCD_CTRL|=(1<<GLCD_CS2); if(flags & _RS) GLCD_CTRL|=(1<<GLCD_RS); else GLCD_CTRL&=~(1<<GLCD_RS); GLCD_PORT=0x0; GLCD_DDR=0; // entrada GLCD_CTRL |=(1<<GLCD_RW); //1 lectura GLCD_CTRL |= (1<<GLCD_EN); //1 _delay_us(10); temp=GLCD_IN; GLCD_CTRL &=~(1<<GLCD_EN); //0 return temp; } void glcd_general_write(char data,char flags){ //char i=0; if(flags & _CS1) GLCD_CTRL|=(1<<GLCD_CS1); else GLCD_CTRL&=~(1<<GLCD_CS1); if(flags & _CS2) GLCD_CTRL|=(1<<GLCD_CS2); else GLCD_CTRL&=~(1<<GLCD_CS2); GLCD_DDR=0xFF; // salida GLCD_PORT=data; if(flags & _RS) GLCD_CTRL|=(1<<GLCD_RS); else GLCD_CTRL&=~(1<<GLCD_RS); GLCD_CTRL &=~(1<<GLCD_RW); //0 escritura GLCD_CTRL |= (1<<GLCD_EN); //1 _delay_us(1); GLCD_CTRL &=~(1<<GLCD_EN); //0 _delay_us(9); //_delay_us(20); //while((++i)&&(glcd_status(flags) & BUSY)); } unsigned char gx,gy,gcs; void glcd_goto(unsigned char x,unsigned char y){ if(x>=128){x=0;y++;} if(y>=8) y=0; glcd_cmd(GLCD_X(y),_BOTH); if(x<64){ //primer controlador... gcs=_CS1; glcd_cmd(GLCD_Y(x),_CS1); }else{ //segundo controlador... gcs=_CS2; glcd_cmd(GLCD_Y(x),_CS2);//la macro le quita el 64 } gx=x;gy=y; } #ifdef GLCD_USE_GRAPHICS #ifndef GLCD_USE_READ unsigned char glcd_matrix[128][8]; //internal matrix... just for drawing... //theorically, you could read the data directly from // the GLCD, but proteus doesn't like that way... #endif void glcd_set_pixel(unsigned char x,unsigned char y){ unsigned char page,bv,temp; Ing. Germán Jesús Pereira Muñoz Página 23 x &=0x7F; y&=0x3F; page=y>>3; bv=1<<(y&7); glcd_goto(x,page); #ifdef GLCD_USE_READ temp=glcd_read(gcs); temp=glcd_read(gcs); glcd_goto(x,page); #else temp=glcd_matrix[x][page]; temp|=bv; glcd_matrix[x][page]=temp; #endif glcd_dat(temp,gcs); } void glcd_clear_pixel(unsigned char x,unsigned char y){ unsigned char page,bv,temp; x &=0x7F; y&=0x3F; page=y>>3; bv=1<<(y&7); glcd_goto(x,page); #ifdef GLCD_USE_READ temp=glcd_read(gcs); temp=glcd_read(gcs); glcd_goto(x,page); #else temp=glcd_matrix[x][page]; temp&=~bv; glcd_matrix[x][page]=temp; #endif glcd_dat(temp,gcs); } void glcd_line(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2){ signed char deltax,deltay,dirx,diry; unsigned char acc=0,cnt; deltax=x2-x1; if((deltax)<0){ deltax=-deltax; dirx=-1; }else dirx=1; deltay=y2-y1; if((deltay)<0){ deltay=-deltay; diry=-1; }else diry=1; glcd_set_pixel(x1,y1); if(deltay>deltax){ cnt=deltay; while(cnt--){ y1+=diry; acc+=deltax; if(acc>=deltay){ acc-=deltay; x1+=dirx; } glcd_set_pixel(x1,y1); } } else{ cnt=deltax; while(cnt--){ x1+=dirx; acc+=deltay; if(acc>=deltax){ acc-=deltax; y1+=diry; } glcd_set_pixel(x1,y1); Ing. Germán Jesús Pereira Muñoz Página 24 } } } void glcd_rectangle(unsigned char x,unsigned char y, unsigned char a,unsigned char b){ unsigned char j; for (j = 0; j < a; j++) { glcd_set_pixel(x, y + j); glcd_set_pixel(x + b - 1, y + j); } for (j = 0; j < b; j++) { glcd_set_pixel(x + j, y); glcd_set_pixel(x + j, y + a - 1); } } void glcd_circle(unsigned char xcenter, unsigned char ycenter, unsigned char radius){ int tswitch, y, x = 0; unsigned char d; d = ycenter - xcenter; y = radius; tswitch = 3 - 2 * radius; while (x <= y) { glcd_set_pixel(xcenter + x, ycenter + y); glcd_set_pixel(xcenter + x, ycenter - y); glcd_set_pixel(xcenter - x, ycenter + y); glcd_set_pixel(xcenter - x, ycenter - y); glcd_set_pixel(ycenter + y - d, ycenter + x); glcd_set_pixel(ycenter + y - d, ycenter - x); glcd_set_pixel(ycenter - y - d, ycenter + x); glcd_set_pixel(ycenter - y - d, ycenter - x); if (tswitch < 0) tswitch += (4 * x + 6); else { tswitch += (4 * (x - y) + 10); y--; } x++; } } #endif //GLCD_USE_GRAPHICS #ifdef GLCD_USE_TEXT #include <avr/pgmspace.h> prog_char glcd_cg[][5]={ {0x00,0x00,0x00,0x00,0x00}, // {0x00,0x00,0x4f,0x00,0x00}, //! {0x00,0x07,0x00,0x07,0x00}, //" {0x14,0x7f,0x14,0x7f,0x14}, //# {0x24,0x2a,0x7f,0x2a,0x12}, //$ {0x23,0x13,0x08,0x64,0x62}, //% {0x36,0x49,0x55,0x22,0x50}, //& {0x00,0x00,0x07,0x00,0x00}, //' {0x00,0x1c,0x22,0x41,0x00}, //( {0x00,0x41,0x22,0x1c,0x00}, //) {0x14,0x08,0x3e,0x08,0x14}, //* {0x08,0x08,0x3e,0x08,0x08}, //+ {0x00,0x50,0x30,0x00,0x00}, //, {0x08,0x08,0x08,0x08,0x08}, //- {0x00,0x60,0x60,0x00,0x00}, //. {0x20,0x10,0x08,0x04,0x02}, /// {0x3e,0x51,0x49,0x45,0x3e}, //0 {0x00,0x42,0x7f,0x40,0x00}, //1 {0x62,0x51,0x49,0x45,0x42}, //2 {0x21,0x41,0x45,0x4b,0x31}, //3 {0x18,0x14,0x12,0x7f,0x10}, //4 {0x27,0x45,0x45,0x45,0x39}, //5 {0x3c,0x4a,0x49,0x49,0x30}, //6 {0x01,0x71,0x09,0x05,0x03}, //7 Ing. Germán Jesús Pereira Muñoz Página 25 {0x36,0x49,0x49,0x49,0x36}, //8 {0x06,0x49,0x49,0x29,0x1e}, //9 {0x00,0x36,0x36,0x00,0x00}, //: {0x00,0x76,0x36,0x00,0x00}, //; {0x08,0x14,0x22,0x41,0x00}, //< {0x14,0x14,0x14,0x14,0x14}, //={0x00,0x41,0x22,0x14,0x08}, //> {0x02,0x01,0x51,0x09,0x06}, //? {0x3e,0x41,0x5d,0x55,0x1e}, //@ {0x7e,0x11,0x11,0x11,0x7e}, //A {0x7f,0x49,0x49,0x49,0x36}, //B {0x3e,0x41,0x41,0x41,0x22}, //C {0x7f,0x41,0x41,0x22,0x1C}, //D {0x7f,0x49,0x49,0x49,0x41}, //E {0x7f,0x09,0x09,0x09,0x01}, //F {0x3e,0x41,0x49,0x49,0x7a}, //G {0x7f,0x08,0x08,0x08,0x7f}, //H {0x00,0x41,0x7f,0x41,0x00}, //I {0x20,0x40,0x41,0x3f,0x01}, //J {0x7f,0x08,0x14,0x22,0x41}, //K {0x7f,0x40,0x40,0x40,0x40}, //L {0x7f,0x02,0x0c,0x02,0x7f}, //M {0x7f,0x04,0x08,0x10,0x7f}, //N {0x3e,0x41,0x41,0x41,0x3e}, //O {0x7f,0x09,0x09,0x09,0x06}, //P {0x3e,0x41,0x51,0x21,0x5e}, //Q {0x7f,0x09,0x19,0x29,0x46}, //R {0x26,0x49,0x49,0x49,0x32}, //S {0x01,0x01,0x7f,0x01,0x01}, //T {0x3f,0x40,0x40,0x40,0x3f}, //U {0x1f,0x20,0x40,0x20,0x1f}, //V {0x3f,0x40,0x38,0x40,0x3f}, //W {0x63,0x14,0x08,0x14,0x63}, //X {0x07,0x08,0x70,0x08,0x07}, //Y {0x61,0x51,0x49,0x45,0x43}, //Z {0x00,0x7f,0x41,0x41,0x00}, //[ {0x02,0x04,0x08,0x10,0x20}, // /invertido {0x00,0x41,0x41,0x7f,0x00}, //] {0x04,0x02,0x01,0x02,0x04}, //^ {0x40,0x40,0x40,0x40,0x40}, //_ {0x00,0x00,0x03,0x05,0x00}, //` {0x20,0x54,0x54,0x54,0x78}, //a {0x7f,0x44,0x44,0x44,0x38}, //b {0x38,0x44,0x44,0x44,0x44}, //c {0x38,0x44,0x44,0x44,0x3F}, //d {0x38,0x54,0x54,0x54,0x18}, //e {0x04,0x04,0x7e,0x05,0x05}, //f {0x08,0x54,0x54,0x54,0x3c}, //g {0x7f,0x08,0x04,0x04,0x78}, //h {0x00,0x44,0x7d,0x40,0x00}, //i {0x40,0x40,0x44,0x3d,0x00}, //j {0x7f,0x10,0x28,0x44,0x00}, //k {0x00,0x41,0x7f,0x40,0x00}, //l {0x7c,0x04,0x7c,0x04,0x78}, //m {0x7c,0x08,0x04,0x04,0x78}, //n {0x38,0x44,0x44,0x44,0x38}, //o {0x7c,0x24,0x24,0x24,0x18}, //p {0x18,0x24,0x24,0x24,0x7C}, //q {0x7c,0x08,0x04,0x04,0x00}, //r {0x48,0x54,0x54,0x54,0x24}, //s {0x04,0x04,0x3F,0x44,0x44}, //t {0x3c,0x40,0x40,0x7c,0x40}, //u {0x1c,0x20,0x40,0x20,0x1c}, //v {0x3c,0x40,0x30,0x40,0x3c}, //w {0x44,0x28,0x10,0x28,0x44}, //x {0x0c,0x50,0x50,0x50,0x3c}, //y {0x44,0x64,0x54,0x4C,0x44}, //z {0x08,0x36,0x41,0x41,0x00}, //{ {0x00,0x00,0x77,0x00,0x00}, //| {0x00,0x41,0x41,0x36,0x08}, //} {0x08,0x04,0x08,0x10,0x08}, //~ {0x3E,0x22,0x22,0x22,0x3E}, //127 {0x3e,0x55,0x55,0x41,0x22}, //? {0x3E,0x22,0x22,0x22,0x3E}, //• Ing. Germán Jesús Pereira Muñoz Página 26 {0x00,0xA0,0x60,0x00,0x00}, //? {0x44,0x44,0x3e,0x05,0x05}, //? {0x20,0x10,0x20,0x40,0x20}, //? {0x00,0x40,0x40,0x40,0x00}, //? {0x00,0x40,0x7F,0x40,0x00}, //? {0x00,0x24,0xFF,0x24,0x00}, //? {0x00,0x02,0x01,0x02,0x00}, //? {0x13,0x6B,0x64,0x62,0x61}, //? {0x58,0x55,0x56,0x55,0x68}, //? {0x00,0x08,0x14,0x22,0x00}, //? {0x3E,0x41,0x7f,0x49,0x41}, //? {0x3E,0x22,0x22,0x22,0x3E}, //• {0x3E,0x22,0x22,0x22,0x3E}, //• {0x3E,0x22,0x22,0x22,0x3E}, //• {0x3E,0x22,0x22,0x22,0x3E}, //• {0x00,0x00,0x01,0x02,0x00}, //? {0x00,0x02,0x01,0x00,0x00}, //? {0x00,0x01,0x02,0x01,0x02}, //? {0x02,0x01,0x02,0x01,0x00}, //? {0x00,0x1c,0x1c,0x1c,0x00}, //? {0x00,0x08,0x08,0x08,0x00}, //- {0x08,0x08,0x08,0x08,0x08}, //- {0x07,0x07,0x00,0x07,0x07}, //? {0x01,0x07,0x01,0x07,0x07}, //? {0x48,0x55,0x56,0x55,0x24}, //? {0x00,0x22,0x14,0x08,0x00}, //? {0x3C,0x44,0x78,0x54,0x58}, //? {0x3E,0x22,0x22,0x22,0x3E}, //• {0x3E,0x22,0x22,0x22,0x3E}, //• {0x0E,0x09,0x70,0x09,0x0E}, //? {0x00,0x00,0x00,0x00,0x00}, // {0x00,0x00,0x79,0x00,0x00}, //¡ {0x38,0x44,0xc6,0x44,0x44}, //¢ {0x4f,0x60,0x50,0x40,0x60}, //£ {0x44,0x28,0x28,0x28,0x44}, //¤ {0x07,0x28,0x70,0x28,0x07}, //¥ {0x00,0x00,0x77,0x00,0x00}, //¦ {0x26,0x55,0x55,0x55,0x32}, //§ {0x00,0x01,0x00,0x01,0x00}, //¨ {0x3E,0x49,0x55,0x55,0x3E}, //© {0x08,0x15,0x15,0x1E,0x00}, //ª {0x08,0x14,0x2A,0x14,0x22}, //« {0x00,0x08,0x08,0x38,0x00}, //¬ {0x00,0x08,0x08,0x08,0x00}, //- {0x3E,0x5D,0x4D,0x55,0x3E}, //® {0x01,0x01,0x01,0x01,0x01}, //¯ {0x00,0x02,0x05,0x02,0x00}, //° {0x28,0x28,0x3e,0x28,0x28}, //± {0x00,0x0D,0x0D,0x0a,0x00}, //² {0x00,0x09,0x0B,0x07,0x00}, //³ {0x00,0x00,0x02,0x01,0x00}, //´ {0x40,0x7c,0x40,0x40,0x3c}, //µ {0x06,0x09,0x7f,0x09,0x7F}, //¶ {0x00,0x1c,0x1c,0x1c,0x00}, //· {0x40,0xa8,0xa8,0xF0,0x00}, //¸ {0x00,0x09,0x0F,0x08,0x00}, //¹ {0x00,0x06,0x09,0x06,0x00}, //º {0x22,0x14,0x2a,0x14,0x08}, //» {0x24,0x17,0x38,0x24,0x07}, //¼ {0x24,0x17,0x58,0x74,0x52}, //½ {0x25,0x17,0x38,0x24,0x07}, //¾ {0x30,0x48,0x45,0x40,0x20}, //¿ {0x78,0x25,0x26,0x24,0x78}, // {0x78,0x24,0x26,0x25,0x78}, //M {0x78,0x26,0x25,0x26,0x78}, //• {0x78,0x25,0x25,0x25,0x78}, //Í {0x78,0x25,0x24,0x25,0x78}, //c {0x78,0x24,0x25,0x24,0x78}, //o {0x7E,0x09,0x7F,0x49,0x41}, //? {0x1e,0x21,0x61,0x21,0x12}, //A {0xFC,0x95,0x96,0x94,0x84}, //? {0xFC,0x94,0x96,0x95,0x84}, //? {0xFC,0x96,0x95,0x96,0x84}, //? {0xFC,0x95,0x94,0x95,0x84}, //_ Ing. Germán Jesús Pereira Muñoz Página 27 {0x00,0x45,0x7E,0x44,0x00}, //? {0x00,0x44,0x7E,0x45,0x00}, //? {0x00,0x46,0x7D,0x46,0x00}, //? {0x00,0x45,0x7C,0x45,0x00}, //? {0x7f,0x49,0x49,0x22,0x28}, //? {0x7C,0x09,0x11,0x21,0x7C}, //? {0x78,0x85,0x86,0x84,0x78}, //? {0x78,0x84,0x86,0x85,0x78}, //? {0x78,0x86,0x85,0x86,0x78}, //? {0x78,0x85,0x85,0x85,0x78}, //? {0x78,0x85,0x84,0x85,0x78}, //? {0x00,0x05,0x02,0x05,0x00}, //? {0x3e,0x71,0x49,0x43,0x3e}, //? {0x7C,0x81,0x82,0x80,0x7C}, //? {0x7C,0x80,0x82,0x81,0x7C}, //? {0x7C,0x82,0x81,0x82,0x7C}, //? {0x7C,0x81,0x80,0x81,0x7C}, //? {0x0E,0x08,0x72,0x09,0x0E}, //? {0x41,0x7F,0x55,0x55,0x08}, //? {0x40,0x7f,0x41,0x49,0x36}, //? {0x20,0x55,0x56,0x54,0x78}, //J {0x20,0x54,0x56,0x55,0x78}, //? {0x20,0x56,0x55,0x56,0x78}, //? {0x20,0x55,0x55,0x55,0x78}, //? {0x20,0x55,0x54,0x55,0x78}, //? {0x20,0x54,0x56,0x56,0x78}, //? {0x35,0x54,0x78,0x54,0x58}, //? {0x1C,0x22,0x62,0x22,0x22}, //? {0x38,0x55,0x56,0x54,0x18}, //? {0x38,0x56,0x55,0x54,0x18}, //? {0x38,0x56,0x55,0x56,0x18}, //? {0x38,0x55,0x54,0x55,0x18}, //? {0x00,0x45,0x7A,0x40,0x00}, //? {0x00,0x44,0x7A,0x41,0x00}, //? {0x00,0x46,0x7d,0x42,0x00}, //? {0x00,0x45,0x7C,0x41,0x00}, //? {0x38,0x47,0x45,0x47,0x38}, //J {0x7c,0x09,0x05,0x05,0x78}, //? {0x38,0x45,0x46,0x44,0x38}, //? {0x38,0x44,0x46,0x45,0x38}, //? {0x38,0x46,0x45,0x46,0x38}, //? {0x38,0x45,0x45,0x45,0x38}, //? {0x38,0x45,0x44,0x45,0x38}, //? {0x00,0x08,0x2A,0x08,0x00}, //? {0x38,0x64,0x54,0x4C,0x38}, //? {0x3c,0x41,0x42,0x7c,0x40}, //? {0x3c,0x40,0x42,0x7D,0x40}, //? {0x3c,0x42,0x41,0x7E,0x40}, //? {0x3D,0x40,0x40,0x7D,0x40}, //? {0x0c,0x50,0x52,0x51,0x3c}, //? {0x41,0x7F,0x14,0x14,0x08}, //? {0x0c,0x51,0x50,0x51,0x3c} //? }; char gstyle; void glcd_chr(unsigned char chr){ // escribe en la posicion ctual... unsigned char i,d; if(chr<0x20){ if((chr==13)|(chr==10)) glcd_crlf(); // mejor... if(chr==27) glcd_clear(); if(chr!=9) return; chr=0x20; } if(gx>((gstyle & GLCD_BOLD)?117:123)) glcd_crlf(); for(i=0;i<5;i++){ d=pgm_read_byte(&glcd_cg[chr-0x20][i]); if(gstyle & GLCD_NEG) d=~d; // negado glcd_byte(d); if(gstyle & GLCD_BOLD) glcd_byte(d); //repete para negrita. } Ing. Germán Jesús Pereira Muñoz Página 28 if (gx<127) glcd_byte((gstyle & GLCD_NEG)? 0xFF:0x00); } #endif //GLCD_USE_TEXT void glcd_byte(unsigned char data){ if(gx==64){ gcs=_CS2; glcd_cmd(GLCD_Y(0),_CS2); } if(gx==128) glcd_crlf(); glcd_dat(data,gcs); #if defined(GLCD_USE_GRAPHICS) && !defined(GLCD_USE_READ) glcd_matrix[gx][gy]=data;//directly! #endif gx++; // el contador interno tambi?crece... } void glcd_clear(void){ unsigned char i,j; for(j=0;j<8;j++){ glcd_cmd(GLCD_X(j),_BOTH); //primera fila (de 8 (de 8 bits)) glcd_cmd(GLCD_Y(0),_BOTH);//primera columna (de 64) for (i=0;i<64;i++){ glcd_dat(0,_BOTH); // borrar todos los datos de memoria #if defined(GLCD_USE_GRAPHICS) && !defined(GLCD_USE_READ) glcd_matrix[i][j]=0; glcd_matrix[i+64][j]=0; #endif } } glcd_cmd(GLCD_X(0),_BOTH); //primera fila (de 8 (de 8 bits)) glcd_cmd(GLCD_Y(0),_BOTH); //primera columna (de 64) gx=0; gy=0; gcs=_CS1; } void glcd_clear_line(unsigned char y){ unsigned char i; glcd_cmd(GLCD_X(y),_BOTH); glcd_cmd(GLCD_Y(0),_BOTH); //primera columna (de 64) for (i=0;i<64;i++){ glcd_dat(0,_BOTH); // borrar todos los datos de memoria #if defined(GLCD_USE_GRAPHICS) && !defined(GLCD_USE_READ) glcd_matrix[i][y&7]=0; glcd_matrix[i+64][y&7]=0; #endif } glcd_cmd(GLCD_X(y),_BOTH); glcd_cmd(GLCD_Y(0),_BOTH); //primera columna (de 64) gx=0; gy=y; gcs=_CS1; } #ifdef GLCD_USE_STDOUT static FILE mystdout = FDEV_SETUP_STREAM(glcd_putchar,NULL,_FDEV_SETUP_WRITE); int glcd_putchar(char c, FILE *stream) { glcd_chr(c); // mantiene el estilo... return 0; } #endif void glcd_init(void){ GLCD_PORT=0; GLCD_DDR=0xFF; // salida GLCD_CTRL&=~((1<<GLCD_RS)|(1<<GLCD_RW)|(1<<GLCD_EN) |(1<<GLCD_CS1)|(1<<GLCD_CS2)|(1<<GLCD_RST)); GLCD_CDDR|=(1<<GLCD_RS)|(1<<GLCD_RW)|(1<<GLCD_EN) |(1<<GLCD_CS1)|(1<<GLCD_CS2)|(1<<GLCD_RST); Ing. Germán Jesús Pereira Muñoz Página 29 _delay_ms(1); GLCD_CTRL|=(1<<GLCD_RST); //nunca mas... _delay_ms(1); glcd_off(); glcd_cmd(GLCD_Z(0),_BOTH); //coordenadas iniciales glcd_clear(); glcd_on(); // encender! #ifdef GLCD_USE_STDOUT stdout = &mystdout; #endif} El programa anterior tiene todos los símbolos que se pueden introducir a la pantalla graficadora y las funciones para llamarla como en el caso anterior. El nexo, con algunos comentarios: #ifndef GLCD_H #define GLCD_H //comentar la siguiente linea si no se usará la pantalla para //funciones gráficas #define GLCD_USE_GRAPHICS //comentar la siguiente linea se no se usará la pantalla para //dseplegar texto... #define GLCD_USE_TEXT //comentar la siguiente línea se no se usará la pantalla como //dispositivo estándar de salida [stdout para printf y printf_P] #define GLCD_USE_STDOUT //comentar la siguiente línea se se desea una matrix para //los gráficos, o leer los datos directamente; //#define GLCD_USE_READ //se debe comentar para simular en Proteus! //configuracion de pines, se utiliza dos puertos, //uno de 8 bits para datos/comando y otros 6 pines para control //Puerto de Datos #define GLCD_PORT PORTB #define GLCD_IN PINB #define GLCD_DDR DDRB //Puerto de Control #define GLCD_CTRL PORTC #define GLCD_CDDR DDRC //pines de control en el puerto de control #define GLCD_RS PC0 #define GLCD_RW PC1 #define GLCD_EN PC2 #define GLCD_CS1 PC3 #define GLCD_CS2 PC4 #define GLCD_RST PC5 //fin de configuración de Hardware... /*********************************************************************/ #if defined(GLCD_USE_STDOUT) && !defined(GLCD_USE_TEXT) #error "Configure glcd.h para usar texto si desea utilizarlo como dispositivo estándar de salida..." #endif //definicion de posibles chip select, para optimizar las rutinas #define _CS1 1 #define _CS2 2 #define _RS 16 #define _BOTH (_CS1|_CS2) #define _BUSY 0x80 //bit 7 de estado Ing. Germán Jesús Pereira Muñoz Página 30 //comandos de pantalla GLCD #define GLCD_ON 0x3F #define GLCD_OFF 0x3E #define GLCD_Y(y) (0x40|((y)&63)) #define GLCD_X(x) (0xB8|((x)&7)) #define GLCD_Z(z) (0xC0|((z)&63)) void glcd_general_write(char data,char flags); #define glcd_cmd(cmd,cs) glcd_general_write(cmd,cs) #define glcd_dat(dat,cs) glcd_general_write(dat,(cs)|_RS) unsigned char glcd_general_read(char flags); #define glcd_read(cs) glcd_general_read((cs)|_RS) void glcd_init(void); void glcd_clear(void); void glcd_clear_line(unsigned char y); #define glcd_on() glcd_cmd(GLCD_ON,_BOTH) #define glcd_off() glcd_cmd(GLCD_OFF,_BOTH) extern unsigned char gx,gy,gcs; void glcd_byte(unsigned char data); void glcd_goto(unsigned char x,unsigned char y); #define glcd_cr() glcd_cmd(GLCD_Y(gx=0),gcs=_CS1) #define glcd_lf() glcd_cmd(GLCD_X(++gy),_BOTH) #define glcd_crlf() {glcd_cr();glcd_lf();} #ifdef GLCD_USE_GRAPHICS void glcd_set_pixel(unsigned char x,unsigned char y); void glcd_clear_pixel(unsigned char x,unsigned char y); void glcd_rectangle(unsigned char x,unsigned char y, unsigned char a,unsigned char b); void glcd_circle(unsigned char xcenter, unsigned char ycenter, unsigned char radius); void glcd_line(unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2); #endif //GLCD_USE_GRAPHICS #ifdef GLCD_USE_TEXT void glcd_chr(unsigned char chr); extern char gstyle; #define set_glcd_style(a) gstyle=a #define GLCD_NEG 0x01 #define GLCD_BOLD 0x02 #ifdef GLCD_USE_STDOUT #include <stdio.h> int glcd_putchar(char c, FILE *stream); #endif #endif //GLCD_USE_TEXT #endif El programa principal entonces solo llama a las funciones anteriores. Por ejemplo para graficar un eje de coordenadas y unos botones: #include <avr/io.h> #include <avr/pgmspace.h> #include <util/delay.h> #include "glcd.h" void delay_ms(unsigned int n){ while(n--) _delay_ms(1); Ing. Germán Jesús Pereira Muñoz Página 31 } int main(void){ int i=0; glcd_init(); glcd_rectangle(55,40,20,20); glcd_rectangle(10,40,20,20); glcd_rectangle(100,40,20,20); glcd_goto(17,6); glcd_chr('A'); glcd_goto(62,6); glcd_chr('B'); glcd_goto(107,6); glcd_chr('C'); glcd_line(10,18,125,18); glcd_line(65,30,65,0); glcd_goto(10,2); glcd_chr('<'); glcd_goto(120,2); glcd_chr('>'); glcd_goto(63,8); glcd_chr('^'); glcd_goto(63,4); glcd_chr('v'); glcd_goto(70,8); glcd_chr('y'); glcd_goto(120,3); glcd_chr('x'); delay_ms(1000); while(1){ //gstyle ^=(GLCD_NEG); delay_ms(100); } } C S 1 1 C S 2 2 G N D 3 V C C 4 V 0 5 D I 6 R /W 7 E 8 D B 0 9 D B 1 1 0 D B 2 1 1 D B 3 1 2 D B 4 1 3 D B 5 1 4 D B 6 1 5 D B 7 1 6 R S T 1 7 -V o u t 1 8 LCD1 LGM12641BS1R PA0/ADC0 40 PA1/ADC1 39 PA2/ADC2 38 PA3/ADC3 37 PA4/ADC4 36 PA5/ADC5 35 PA6/ADC6 34 PB0/XCK/T0 1 PB1/T1 2 PB2/INT2/AIN0 3 PB3/OC0/AIN1 4 PB4/SS 5 PB5/MOSI 6 PB6/MISO 7 PB7/SCK 8 PA7/ADC7 33 RESET 9 XTAL1 13 XTAL2 12 PC0/SCL 22 PC1/SDA 23 PC2/TCK 24 PC3/TMS 25 PC4/TDO 26 PC5/TDI 27 PC6/TOSC1 28 PC7/TOSC2 29 PD0/RXD 14 PD1/TXD 15 PD2/INT0 16 PD3/INT1 17 PD4/OC1B 18 PD5/OC1A 19 PD6/ICP 20 PD7/OC2 21 AVCC 30 AREF 32 U1 ATMEGA32 R1 10k R2 10k 6.6. Manejo de teclado matricial. Ing. Germán Jesús Pereira Muñoz Página 32 Un teclado matricial es mucho más fácil de controlar que una pantalla como la anterior. Este solo testea un puerto para conocer el botón que se presionó. Para un teclado 4x4: #include<avr/io.h> #include<util/delay.h> #include "teclado.h" #include<avr/pgmspace.h> prog_char tecla[4][4]= {{'7','8','9','/'}, {'4','5','6','*'}, {'1','2','3','-'}, {'c','0','=','+'}}; char leer(void){ char i,j,k,m; k=(1<<T_FILA_2)|(1<<T_FILA_3)|(1<<T_FILA_4);/* TAMBIÉN PODEMOS PONER (15<<T_COL_1);*/ for(i=0;i<4;i++){ T_FILAS_PORT=k; retardo(); m=T_COL_PIN; m&=(15<<T_COL_1); if(m!=(15<<T_COL_1)){ for(j=0;j<4;j++){ if((m&(1<<T_COL_1))==0){ return pgm_read_byte(&tecla[i][j]);/*para retornar el valor, ya que return no funciona*/ } m>>=1; }} k<<=1; k|=(1<<T_FILA_1);/*estas dos últimas para rotar el cero a la izq*/ } return 0; } char espera(void){ char t; do{ t=leer(); }while(t==0); while(leer()!=0) retardo(); return t;} void inicia(void){ T_FILAS_PORT=0; T_FILAS_DDR=(15<<T_FILA_1); T_COL_DDR=0; T_COL_PORT=(15<<T_COL_1); } Lo que realiza este programa es definir los botones que se usara en una matriz, luegolos ‘for’ testean cada botón moviendo por filas y columnas y cuando detectan un cero (cuando alguien presiona un botón) entonces se conoce la posición del botón y asi su símbolo definido anteriormente y ese se devuelve como dato. El programa nexo: #define T_FILAS_PORT PORTB #define T_FILAS_DDR DDRB #define T_FILA_1 PB2 #define T_FILA_2 PB3 #define T_FILA_3 PB4 #define T_FILA_4 PB5 #define T_COL_PORT PORTC #define T_COL_DDR DDRC #define T_COL_PIN PINC #define T_COL_1 PC0 #define T_COL_2 PC1 Ing. Germán Jesús Pereira Muñoz Página 33 #define T_COL_3 PC2 #define T_COL_4 PC3 #define retardo() _delay_ms(1) char leer(void); char espera(void); void inicia(void); Este solo define las filas y columnas y las funciones de esperar dato, leer dato y la de iniciar. Luego, el programa que utiliza este sistema: #include "Lcd.h" #include "teclado.h" #include<stdio.h> int teclado_getchar(FILE*stream){ unsigned char tecla; tecla=espera(); if(tecla=='c'){ Lcd_clear(); return 0; } if ((tecla>='0')&&(tecla<='9')) {Lcd_char(tecla); return tecla; } Lcd_char(tecla); return 0; } FILE teclado=FDEV_SETUP_STREAM(NULL,teclado_getchar,_FDEV_SETUP_READ); int main(void){ int f; inicia(); stdin=&teclado; Lcd_init(); while(1){ Lcd_clear(); scanf("%3i",&f); Lcd_cmd(0xC0); printf("sumado 20 es:%i",f+20); delay_ms(1000); } } Teclado.c nos devuelve el dato en una variable llamada ‘tecla’, entonces solo preguntamos qué valor tiene tecla y así sabremos que botón se presionó. Una vez conocido el dato, nosotros podemos hacer lo que queramos con ese dato. Como por ejemplo del programa sumarle un número como 20. Nótese que el dato introducido es de tipo char y se trabaja como tal y si se requiere trabajar como entero o punto flotante, como en el programa, utilizamos un stream. Ing. Germán Jesús Pereira Muñoz Página 34 PB0/T0/XCK 1 PB1/T1 2 PB2/AIN0/INT2 3 PB3/AIN1/OC0 4 PB4/SS 5 PB5/MOSI 6 PB6/MISO 7 PB7/SCK 8 RESET 9 XTAL2 13 XTAL1 12 PD0/RXD 14 PD1/TXD 15 PD2/INT0 16 PD3/INT1 17 PD4/OC1B 18 PD5/OC1A 19 PD6/ICP1 20 PD7/OC2 21 PC0/SCL 22 PC1/SDA 23 PC2/TCK 24 PC3/TMS 25 PC4/TDO 26 PC5/TDI 27 PC6/TOSC1 28 PC7/TOSC2 29 PA7/ADC7 33 PA6/ADC6 34 PA5/ADC5 35 PA4/ADC4 36 PA3/ADC3 37 PA2/ADC2 38 PA1/ADC1 39 PA0/ADC0 40 AREF 32 AVCC 30 U1 ATMEGA32 D 7 1 4 D 6 1 3 D 5 1 2 D 4 1 1 D 3 1 0 D 2 9 D 1 8 D 0 7 E 6 R W 5 R S 4 V S S 1 V D D 2 V E E 3 LCD1 LM016L RV1 10 1 2 3 654 8 9 = 7 ++C ON 0 A B C D 1 2 43 6.7. Manejo de interrupciones. El AVR cuenta con distintas interrupciones, como por ejemplo las más usuales, la temporizada y la externa. Manejarlas es muy sencillo ya que se tiene predeterminado una función donde se salta el programa para atender a la interrupción. Esta es: ISR (‘nombre’_COMP_vect) {}. Donde en ‘nombre’ colocamos el nombre de la interrupción a utilizar. Entonces para un reloj digital, utilizando la interrupción temporizada tenemos: #include <avr/io.h> #include "lcd.h" #include <avr/interrupt.h> volatile char h,m,s,c; volatile char contador; void timer_init (void) { contador=0; TCNT0=0; OCR0=74; TCCR0=(1<<WGM01)|(3<<CS00); TIMSK|=(1<<OCIE0); } ISR (TIMER0_COMP_vect) { if(++contador>=25) { contador=0; if(++c<100) return; c=0; if(++s<60) return; s=0; if(++m<60) return; m=0; if(++h<24) return; h=0; } } El programa realiza la cuenta de un reloj desde centésimas de segundo y se tiene el cálculo efectuado de la siguiente manera: Para un cristal de 12Mhz tenemos que cada 10ms seria 120000 cuentas. Pero el contador TNT0 solo va de 0 a 255 entonces escalamos y el valor exacto para la división Ing. Germán Jesús Pereira Muñoz Página 35 es 64 con lo que tenemos 1875 que sigue siendo mayor a 255. Pero 75x25=1875 y entonces contamos hasta 75 y creamos un contador hasta 25 para incrementar una centésima de segundo. Los demás registros ayudan a configurar este detalle. (ver la hoja de datos) El programa principal no realiza nada más que imprimir los valores de centésima, segundo, minuto y hora: #include <avr/io.h> #include "lcd.h" #include <stdio.h> #include <avr/interrupt.h> void timer_init(void); extern volatile char h,m,s,c; //para compartir variables externas int main (void) { Lcd_Init(); timer_init(); sei(); while(1) { Lcd_cmd(0x80); printf("%02i:%02i:%02i-%02i",(unsigned int)h,(unsigned int)m,(unsigned int)s,(unsigned int)c); } } Utilizamos sei() para habilitar todas las interrupciones. PA0/ADC0 40 PA1/ADC1 39 PA2/ADC2 38 PA3/ADC3 37 PA4/ADC4 36 PA5/ADC5 35 PA6/ADC6 34 PB0/XCK/T0 1 PB1/T1 2 PB2/INT2/AIN0 3 PB3/OC0/AIN1 4 PB4/SS 5 PB5/MOSI 6 PB6/MISO 7 PB7/SCK 8 PA7/ADC7 33 RESET 9 XTAL1 13 XTAL2 12 PC0/SCL 22 PC1/SDA 23 PC2/TCK 24 PC3/TMS 25 PC4/TDO 26 PC5/TDI 27 PC6/TOSC1 28 PC7/TOSC2 29 PD0/RXD 14 PD1/TXD 15 PD2/INT0 16 PD3/INT1 17 PD4/OC1B 18 PD5/OC1A 19 PD6/ICP 20 PD7/OC2 21 AVCC 30 AREF 32 U1 ATMEGA32 D 7 1 4 D 6 1 3 D 5 1 2 D 4 1 1 D 3 1 0 D 2 9 D 1 8 D 0 7 E 6 R W 5 R S 4 V S S 1 V D D 2 V E E 3 LCD1 LM016L R1 10k 6.8. Manejo del conversor análogo digital. Una herramienta fundamental para trabajar en tiempo real con el controlador es su conversor análogo digital, el cual es bastante útil para el control de procesos. Un ejemplo bastante sencillo de esta herramienta es el control de temperatura utilizando un sensor como el LM-35. Pero para no complicarnos con el algoritmo de control y los parámetros, solo utilizaremos el conversor para visualizar los datos que manda el sensor en variación de voltaje. El programa para esto es bastante sencillo. Ing. Germán Jesús Pereira Muñoz Página 36 #include<avr/io.h> #include "lcd.h" #include<stdio.h> volatile int a; void iniciar(void){ ADMUX=0x40; SFIOR=0x00; ADCSRA = 0x86; ADCSRA |= (1<<ADSC); } int main (void){ Lcd_init(); iniciar(); while(1){ ADCSRA |=(1<<ADSC); a=ADC; Lcd_cmd(0x80); printf("La temperatura:"); Lcd_cmd(0xC0); printf("%04i C",a);} } El programa solo configura los registros para utilizar conversión por medio del primer canal, con referencia de 5V y con solo 8 bits de los 10 bits de resolución que se tiene. Nótese que el dato se guarda en el registro ADC y que la instrucción ADCSRA |= (1<<ADSC); indica al controlador que convierta. PB0/T0/XCK 1 PB1/T1 2 PB2/AIN0/INT2 3 PB3/AIN1/OC0 4 PB4/SS 5 PB5/MOSI 6 PB6/MISO 7 PB7/SCK 8 RESET 9 XTAL2 13 XTAL1 12 PD0/RXD 14 PD1/TXD 15 PD2/INT0 16 PD3/INT1 17 PD4/OC1B 18 PD5/OC1A 19 PD6/ICP1 20 PD7/OC2 21 PC0/SCL 22 PC1/SDA 23 PC2/TCK 24 PC3/TMS 25 PC4/TDO 26 PC5/TDI 27 PC6/TOSC1 28 PC7/TOSC2 29 PA7/ADC7 33 PA6/ADC6 34 PA5/ADC5 35 PA4/ADC4 36 PA3/ADC3 37 PA2/ADC2 38 PA1/ADC1 39 PA0/ADC0 40 AREF 32 AVCC 30 U1 ATMEGA32 D 7 1 4 D 6 1 3 D 5 1 2 D 4 1 1 D 3 1 0 D 2 9 D 1 8 D 0 7 E 6 R W 5 R S 4 V S S 1 V D D 2 V E E 3 LCD1 LM016L RV1 10 6.0 3 1 VOUT 2 U2 LM35 6.9. Comunicación serial. Una herramienta muy importante para interactuar con una PC es la comunicación serial. Mediante esta se pueden mandar o recibir datos desde el controlador o hacia Ing. Germán Jesús Pereira Muñoz Página 37 este. El microcontrolador ya tiene incluida esta comunicación y solo basta con colocar el dato a enviar en el buffer correspondiente. Si un dato llega también llega a un registro buffer. El programa habilita la comunicación serial para recepción y transmisión. El registro al que llega el dato es UDR y también colocamos el dato ahí para enviar. #include<avr/io.h> void usart_init(void){ UBRRH=0; UBRRL=77; UCSRA=0; UCSRB=(1<<RXEN)|(1<<TXEN); UCSRC=(1<<URSEL)|(3<<UCSZ0); } void usart_tx(char dato){ while((UCSRA&(1<<UDRE))==0); UDR=dato; } char usart_rx(void){ while((UCSRA&(1<<RXC))==0); return UDR;} char rxready(void){ return(UCSRA&(1<<RXC)); } Después de habilitar la comunicación serial podemos ver el programa que llama a estas rutinas. #include<avr/io.h> #include "usart.h" char c; int main (void){ usart_init(); DDRC=0xff; while(1){ c=usart_rx(); usart_tx(c); } } El programa solo inicia la usart y recibe un dato. Luego de recibir, envía. PB0/T0/XCK 1 PB1/T1 2 PB2/AIN0/INT2 3 PB3/AIN1/OC0 4 PB4/SS 5 PB5/MOSI 6 PB6/MISO 7 PB7/SCK 8 RESET 9 XTAL2 13 XTAL1 12 PD0/RXD 14 PD1/TXD 15 PD2/INT0 16 PD3/INT1 17 PD4/OC1B 18 PD5/OC1A 19 PD6/ICP1 20 PD7/OC2 21 PC0/SCL 22 PC1/SDA 23 PC2/TCK 24 PC3/TMS 25 PC4/TDO 26 PC5/TDI 27 PC6/TOSC1 28 PC7/TOSC2 29 PA7/ADC7 33 PA6/ADC6 34 PA5/ADC5 35 PA4/ADC4 36 PA3/ADC3 37 PA2/ADC2 38 PA1/ADC1 39 PA0/ADC0 40 AREF 32 AVCC 30 U1 ATMEGA32 RXD RTS TXD CTS Ing. Germán Jesús Pereira Muñoz Página 38 Microcontroladores de la familia AVR Índice. 1. Introducción a los microcontroladores. 2. Microcontrolador AVR. 3. Arquitectura de un microcontrolador AVR. 4. Software de Programación (WIN-AVR). 5. Grabadores del AVR. 6. Programación. 6.1. Uso de instrucciones básicas entrada/salida en displays. 6.2. Aplicación básica. (Semáforo) 6.3. Multiplexación de displays. 6.4. Manejo de pantalla LCD. 6.5. LCD graficadora. 6.6. Manejo de teclado matricial. 6.7. Manejo de interrupciones. 6.8. Manejo del conversor análogo digital. 6.9. Comunicación serial
Compartir