You are on page 1of 279

CURSO_MICROS

Pgina |1

Mdulo 1 ndice Captulo 1: Los microcontroladores


Qu son los micros y para qu sirven? Clasificacin de los microcontroladores o Segn la longitud del bus de datos o Segn sus recursos o Segn el set de instrucciones o Segn su arquitectura interna o Segn el fabricante Por qu empezar con los PICs Caractersticas de los PICs Clasificacin de los PICs

Captulo 3: Arquitectura de los PIC16F


Introduccin Caractersticas de los PIC16F84A y PIC16F87xA Diagrama de bloques del PIC16FXXX La memoria de programa o El Contador de Programa o La Pila o STACK La memoria RAM o Registros SFR o Registros GPR Diferencias entre los PIC16F87xA Fuses de Configuracin o Seleccin del oscilador o El Watchdog o Power Up Timer o Proteccin de Cdigo o Escribir memoria FLASH o Modo ICD o Proteccin de EEPROM o Programacin LPV o Brown Out Reset El registro STATUS

Captulo 4: Los Puertos de los PIC

Descripcin de los puertos o El puerto B o El puerto A o El puerto C o El puerto D o El puerto E Configuracin de puertos Resistencias de Pull-up Prctica 4-1 o Ensamblado del programa

CURSO_MICROS

Pgina |2

Prctica 4-2 Prctica 4-3

Captulo 7: Proteus VSM


Qu es Proteus VSM? ISIS Proteus Dibujando el circuito Edicin de objetos Simulacin del diseo Depuracin del programa o Ventanas de depuracin o Ventana Watch Window Instrumentos virtuales o COUNTER TIMER o OSCILLOSCOPE Grficos de simulacin Depuracin con *.sdi Propiedades avanzadas Tutorial de Proteus: Resumen del captulo

1 - Los microcontroladores Los PICs Por qu empezar con los PICs


Por su fcil adquisicin. Se pueden conseguir en casi cualquier tienda de electrnica. Por su pequeo set de instrucciones, que no logra ser igualado por ningn otro microcontrolador. Es casi mgica la forma cmo se pueden implementar fcilmente casi cualquier algoritmo de programa con solo sus 35 instrucciones bsicas. Por su bajo costo. Los PICs son tal vez los microcontroladores ms baratos con las caractersticas que poseen. Por su fcil aprendizaje. Los PICs cuentan con el menor conjunto de instrucciones, y no por ello menos eficientes, que los convierten de lejos en los de mejor aprendizaje. Por la disponibilidad de herramientas. Las herramientas de hardware y software son de amplio alcance. Eso nos permitir empezar muy pronto con la experimentacin sin la preocupacin por mayores recursos.

Caractersticas de los PICs Algunas de las caractersticas y recursos generales y comunes a casi todos los PICs son:

Estn fabricados con tecnologa CMOS. Aunque los dispositivos CMOS son ms lentos que los TTL, son ideales para los microcontroladores porque requieren de menor consumo de energa. Es posible implementar sistemas que solo se alimenten de bateras corrientes. La tecnologa CMOS, como sabemos, tambin

CURSO_MICROS

Pgina |3

significa que los transistores, al ser mucho menos, ocupan mucho menor espacio en el chip. Memorias de programa (FLASH, OTP o ROM), memoria de datos esttica (SRAM) y memoria EEPROM internas. Puertos de E/S bidireccionales configurables independientemente pin por pin. Suministro de alta corriente en los puertos de E/S. Timers. Temporizadores de alta precisin o contadores de pulsos externos. WatchDog. Monitoriza que el PIC funcione adecuadamente a lo que se esperaba y no se cuelgue. ICSP (In Circuit Serial Programming). Permite realizar la programacin del PIC utilizando una interface serial con muy pocos pines. Bits de Configuracin. A veces llamados fuses, permiten establecer un determinado modo de funcionamiento del PIC, como el tipo de oscilador que utilizar o si el cdigo grabado podr o no ser ledo despus de la programacin.

Otros recursos, ms avanzados, son especficos a cada familia de PICs y pueden ser:

Mdulos PWM. Generadores de ondas PWM (Pulse Width Modulation). Son particulamente tiles para controlar la velocidad de los motores DC. Convesores Anlogico-Digital, ADC. Para recibir seales del mundo analgico. Puerto Serial Sncrono, MSSP. Para la comunicacin con dispositivos que utilizan los buses I2C o SPI. Puerto Paralelo Esclavo, SPP. Por ejemplo, para conectarse directamente con el puerto paralelo del PC. USART, Transmisor Receptor Sncrono Asncrono Universal. Para comunicarse mediante los protocolos RS232 con cualquier dispositivo que tambin lo soporte. Por ejemplo, podemos conectar nuestro PIC al puerto serie del PC o a cuanquier otro microcontrolador con USART. Mdulo Comparador Analgico. Nos puede ahorrar un OP-AMP y algo ms. Mdulo CAN. Para facilitarle al PIC su conexin con otros microcontroladores en una pequea red LAN con un protocolo robusto para trabajar en codiciones extremas. Mdulo USB. Casi todos los dispositivos digitales modernos presentan interface USB. Con esto podemos disear sistemas que no tengan nada que envidiarles. Etc., etc.

Clasificacin de los PICs Considerando solo a los microcontroladores de 8 bits, Microchip establece una divisin en cuatro grandes familias:

La familia Baseline. Conformada por los grupos PIC10, PIC12, algunos PIC16 y el PIC14000. Todos tienen instrucciones de 12 bits. La mayora presenta un set de 33 instrucciones y opera a 1MIPS, en tanto que otros cuantos tienen 35 instrucciones y alcanzan los 5MIPS. Tienen muy poca memoria y en cuanto a recursos perifricos, son los menos dotados. La familia Mid-range. Es la familia ms distintiva de los PICs. Engloba los PIC16 y algunos PIC12. Todos tienen 35 instrucciones de 14 bits y ninguno supera los 5MIPS. Sus memorias pueden alcanzar las 8092 palabras de 14 bits. En general, en ellos se pueden encontrar casi todos los recursos hadware buscados en un microcontrolador de 8 bits, por eso se suele tomar de aqu algunos modelos como punto de partida de aprendizaje.

CURSO_MICROS

Pgina |4

La familia High-end. Compuesta por los PIC17, esta familia no lleg a tener gran difusin. La pronta aparicin de los PIC18 ni siquiera le dej tiempo a Microchip para pensar en los modelos con memoria flash. El hecho es que los medios hardware y software de desarrollo requeridos han sido relegados y, por tanto, no vale la pena preocuparse por ellos. La familia High-performance o Enhanced. Est formada por los PIC18. Para un mejor trabajo con los compiladores de alto nivel, su repertorio bsico consta de 75 instrucciones, de 16 bits, y algunos modelos incluyen un pequeo conjunto de instrucciones extendidas. De todos modos, siempre se mantiene la compatibilidad con las 35 instrucciones de los PICs Mid-range. Todos alcanzan velocidades de operacin de hasta 10 MIPS y los PIC18 con USB estn optimizados para trabajar hasta a 12 MIPS. Estn provistos de un modelo de memoria plano, con lo que se ahorran las tediosas operaciones de cambio de banco.

A la vez, puede haber varias decenas de PICs dentro de cada familia, pero las diferencias entre ellos son cada vez menores, como tener algunos pines de E/S ms o menos, tener algo de memoria ms o menos, tener un Timer ms o menos, emplear otro tipo de memoria, y dems detalles de ese tipo

Arquitectura de los PIC16F84A y PIC16F87xA Introduccin Aprender a programar microcontroladores significa aprender a usar todos sus recursos para luego aplicarlos en el diseo deseado. Es un proceso continuo, sistemtico y que demanda algo de paciencia. En este captulo empezaremos por conocer el hardware del PIC. Caractersticas del PIC16F84A y PIC16F87xA A continuacin se citan las caractersticas ms notables del PIC16F84A. Naturalmente, muchas de ellas no las comprenders de plano. Puedes tomar eso como referencia para medir tu avance en el dominio del PICmicro. CPU RISC de Alta Performance:

Solo 35 instrucciones que aprender Velocidad de operacin de hasta 20 MHz (modelo PIC16F84A-20) 1024 palabras de memoria de programa (FLASH) 68 bytes de RAM de Datos esttica 64 bytes de EEPROM de Datos Pila hardware de 8 niveles Cuatro fuentes de interrupcin:

Caractersticas de los Perifricos:


13 pines de E/S con control de direccin individual Suministro de hasta 25 mA de corriente por pin en los puertos TMR0: temporizador/contador de 8-bits con prescaler programable

CURSO_MICROS

Pgina |5

Caractersticas Especiales del Microcontrolador:


Memoria de programa FLASH para 10 000 ciclos de borrado/escritura tpicamente Memoria EEPROM para 10 000 000 de ciclos de borrado/escritura tpicamente Programacin Serial en el Circuito, ICSP Watchdog timer con su propio oscilador RC Capacidad de proteccin de cdigo Modo SLEEP para ahorrar energa Opciones para seleccionar el oscilador

Tecnologa FLASH/EEPROM CMOS mejorada:


Amplio rango de voltaje de operacin: 2.0 V a 5.5 V Mxima disipacin de potencia: 800 mW

Y ahora las caractersticas de los PIC16F87xA: CPU RISC de Alta Performance:


Solo 35 instrucciones que aprender Velocidad de operacin de hasta 20 MHz (200 ns por instruccin bsica) Hasta 8 K de palabras de 14 bits de memoria de programa (FLASH) Hasta 368 bytes de RAM de Datos esttica Hasta 256 bytes de EEPROM de Datos Pila hardware de 8 niveles Hasta 15 fuentes de interrupcin

Caractersticas de los Perifricos:


Hasta 33 pines de E/S con control de direccin individual Suministro de hasta 25 mA de corriente por pin en los puertos Timer0: temporizador/contador de 8-bits con prescaler programable Timer1: temporizador/contador de 16-bits con prescaler programable Timer2: temporizador de 8-bits con prescaler y postcaler programables Dos mdulos CCP, Capture, Compare, PWM. El PWM es de 10 bits MSSP: Puerto Serie Sncrono Maestro con operaciones en modos I2C y SPI. USART: Puerto serie Transmisor Receptor Sncrono Asncrono Universal PSP: Puerto Paralelo Esclavo de 8 bits y con controles RD, WR y CS. BOR: Circuito de Deteccin de Bajo Voltaje

Caractersticas Especiales del Microcontrolador:


Memoria de programa FLASH para 100 000 ciclos de borrado/escritura tpicamente Memoria EEPROM para 1 000 000 de ciclos de borrado/escritura tpicamente Watchdog timer Capacidad de proteccin de cdigo Modo SLEEP para ahorrar energa Opciones para seleccionar el oscilador

CURSO_MICROS

Pgina |6

Programacin Serial en el Circuito, ICSP Autoprogramacin por control software Programacin en bajo voltaje (5V) Soporte ICD va dos pines

Caractersticas analgicas:

Conversor ADC de 10 bits de hasta 8 canales Mdulo Comparador

Diagrama de pines del PIC16F84A y PIC16F877A/874A en encapsulado PDIP.

Diagrama de bloques del PIC16FXXX El siguiente diagrama muestra los principales elementos de un PIC y que tarde o temprano los tendrs que memorizar.

CURSO_MICROS

Pgina |7

Diagrama de bloques simplificado de los PIC16FXXX Ahora una somera descripcin de lo que representan estos bloques.

El CPU es el circuito encargado de leer, decodificar y ejecutar las instrucciones del programa. Su principal componenete a tener en cuenta es el ALU (Unidad Aritmtico Lgica) que se encarga de las operaciones de suma, resta, AND lgica, OR lgica, etc. La Memoria de Programa contiene las instrucciones del programa del PIC. Es una memoria permanente pero que se puede reprogramar para cambiar de tarea. La Memoria de Datos aloja las variables temporales que procesa el CPU. Algunos registros de esta memoria (llamados SFR) pueden controlar los recursos perifricos, como el Timer0, la EEPROM, etc., e inclusive los puertos de E/S. El Contador de Programa es un registro que evoluciona para indicar cul ser la siguiente instruccin que debe ejecutar el CPU. La Pila o Stack es una memoria limitada que solo guarda el valor del Contador de Programa cuando sea necesario. Los perifricos del PIC son elementos que se pueden usar para una determinada tarea; por ejemplo, el Timer0 sirve para temporizaciones. El USART para comunicaciones seriales RS232, etc. Casi todos ellos sern estudiados en un captulo aparte. Los puertos de E/S PORTA,... PORTE son las lneas hacia/desde el exterior donde se pueden conectar los dispositivos a controlar, como LEDs, transistores, LCDs, etc.

Hay ms recursos presentes dentro de un PIC que tambin son imprescindibles pero cuyo trabajo queda en segundo plano. Algunos de ellos sern abordados con detalle en otro momento. La memoria de programa En los PIC16Fxxx es de tipo flash, por eso la F. Aqu es donde se aloja el programa que el CPU ejecutar. Se puede modificar por completo mediante un dispositivo programador por varios miles de veces. Pero tampoco te alegres tanto. No conozco a nadie que haya llegado a ese lmite con un solo PIC. Lo ms probable es que, por ms cuidado que tengas, llegues a freir tu PIC antes de tiempo en algn accidente. Eso es algo muy normal.

CURSO_MICROS

Pgina |8

En los PIC16 la memoria de programa se cuantifica en palabras, de 14 bits cada una. Son de 14 bits porque cada instruccin es de 14 bits. Esto suele impresionar un poco al novel, quien est habituado a medir la capacidad de las memorias en bytes (8 bits). El PIC16F84A tiene 1 k (1024) palabras de memoria. En tiempo de ejecucin son de solo lectura. Con 1 k puede almacenar hasta 1024 instrucciones de cdigo ensamblador. Los PIC16F877A/876A tienen 8 k (8192) palabras de memoria de programa mientras que los PIC16F874A/873A tienen 4 k (4192). Los cuatro PICmicros ofrecen la posibilidad de escribir en su memoria de programa incluso en tiempo de ejecucin. Esta funcin puede ser aprovechada para almacenar datos procesados por el usuario o para permitir la autoprogramacin del PIC. En las siguientes figuras las memorias de programa del PIC estn acompaadas por el PC (Program Counter) y la Pila (Stack). Es as porque hay una extrecha relacin en su trabajo.

Contador de Programa, Pila y Memoria de programa del PIC16F84A y PIC16F87xA. El Contador de Programa, PC El PC es un registro que indica la siguiente instruccin que debe ejecutar el CPU. Si vale 0x0000, ejecutar la primera instruccin de la memoria; si vale 0x0002 ejecutar la tercera instruccin, y as... Al arrancar microcontrolador, el PC vale 0x0000 y se va incrementando automticamente, con lo que el PIC debera ejecutar una a una desde la primera hasta la ltima instruccin del programa. En realidad, en el cdigo habr instrucciones que modifiquen el valor del PC de modo que el programa nunca termine.

CURSO_MICROS

Pgina |9

En los PICs de la familia Mid-Range el PC es de 13 bits, pudiendo direccionar un mximo de 8 k palabras de memoria de programa. La Pila o STACK Es una memoria que almacena temporalmente el valor del PC (Program Counter) cuando el programa llama a una subrutina o cuando salta al Vector de Interrupcin, en una interrupcin. En los PICs de la familia Mid-Range la Pila tiene nicamente 8 niveles y se administra a nivel hardware, esto es, no hay instrucciones para acceder a ella directamente. Su operacin es enteramente en background. Solo debemos cuidar de que no se llegue a desbordar. La memoria RAM A diferencia de las RAMs de los microcontroladores convencionales, que tienen mapa de memoria plano, la RAM de los PICs de familia Mid-range est dividida en sectores denominados bancos. Cada byte de la RAM se conoce como registro. Hay dos tipos de registros de RAM: los registros de funcin especial SFR y los registros de propsito general GPR. No todos los registros estn implementados fsicamente. La escritura en dichas locaciones no significa nada y siempre se leen como 0. El PIC16F84A es de los muy pocos que tienen 2 bancos de RAM; el resto de los PIC16F, que son la gran mayora, tienen 4 bancos. La existencia de los bancos solo es de consideracin (y un dolor de cabeza) cuando se programa en lenguaje ensamblador. Registros de Funciones Especiales, SFR Antes dije que para programar PICs primero haba que conocer sus recursos. Pues bien, todos ellos se pueden controlar mediante los registros SFR. Los registros SFR (Special Function Register) estn ubicados en las primeras posiciones de cada banco de RAM, los que en la figura anterior aparecen con nombre propio, como TMR0, PORTA, etc. Su principal funcin es controlar las operaciones del microprocesador y de los mdulos perifricos. Como se ve, algunos de esos registros aparecen duplicados en los bancos; eso es para facilitarnos su acceso porque son de uso muy frecuente o porque son un poquito ms especiales, como STATUS, INTCON o PCL. Registros de Propsito General, GPR Tambin son registros de 1 byte cada uno. Los registros GPR (General Purpose Register) sirven para almacenar los datos o variables que se procesan en el programa. El PIC16F84A tiene 68 registros GPR, todos ubicados en el banco 0, entre las direcciones 0x0C y 0x4F. En el banco 1 no hay registros GPR, solo hay accesos a los registros del banco 0. Dicho en otras palabras, los registros GPR del banco 1 son un reflejo de los GPR del banco 0.

CURSO_MICROS

P g i n a | 10

Mapa de la RAM de datos del PIC16F84A. Los PIC16F877A/876A tienen 368 registros GPR. Los ltimos 16 registros de los bancos 1, 2 y 3 son los mismos que del banco 0. Los PIC16F874A/873A tienen 192 registros GPR. Su mapa de RAM es un poco diferente debido principalmente a los accesos repetidos. Todas estas diferencias sern de preocupacin solo cuando se programe en ensamblador.

CURSO_MICROS

P g i n a | 11

Mapa de la RAM de datos de los PIC16F877A y PIC16F876A.

CURSO_MICROS

P g i n a | 12

Mapa de la RAM de datos de los PIC16F874A y PIC16F873A. Los registros GPR no tienen nombres propios, pero los compiladores de alto nivel saben cmo administrarlos para almacenar las variables del programa. Por otro lado, si se programa en ensamblador, se les debe acceder mediante sus direcciones, ya sea directamente usando nmeros, o asignndoles nombres a las direcciones con algunas directivas, como equ o cblock. Por ejemplo, en el siguiente cdigo se designan los nombres var1 y var2 a los registros de direcciones 0x25 y 0x30, respectivamente.

CURSO_MICROS

P g i n a | 13

var1 var2

equ equ

0x25 0x30

Luego se podrn usar esos nombres para acceder a los registros designados. Por ejemplo, el siguiente cdigo copia el contenido del primer registro al segundo. movf var1, W movwf var2 ; Mover var1 a W ; Mover W a var2

El diseador puede elegir cualesquiera registros GPR como sus variables de programa. Para ello debe recordar siempre dnde se localizan. Segn las figuras de arriba, en el PIC16F84A estn mapeados a partir de la direccin 0x0C. En los PIC16F87xA empiezan en la direccin 0x20 en el banco 0; los otros bancos se usan raramente en ensamblador. Los Fuses de Configuracin Los fuses del microcontrolador establecen una caracterstica importante en su operacin, tan importante que solo se puede modificar en el momento de programarlo. Por ejemplo, no se podra escoger el tipo de oscilador a usar despus de haber iniciado el programa (sera como cambiarle los neumticos a un automvil en marcha). Los fuses se programan mediante los llamados Bits de Configuracin, los cuales estn contenidos en un registro de EEPROM especial denominado Configuration Word. Este registro est situado en la direccin 0x2007, aunque tiene poco sentido saberlo porque es inaccesible desde el programa en tiempo de ejecucin. Cada aplicacin puede requerir una configuracin particular y si no se establecen los fuses correctos, el programa puede funcionar mal, suponiendo que funcione :). El PIC16F84A tiene 4 fuses y los PIC16F87xA tienen 9. Por tanto habr diferencias en sus palabras de configuracin. sta es la Palabra de Configuracin del PIC16F84A:

sta es la Palabra de Configuracin del PIC16F87xA:

La Palabra de Configuracin de los PIC16F87xA incluye todos los bits del PIC16F84A, aunque varen algunos nombres. De todos modos, esos nombres son raramente usados como tal. Los fuses se suelen programar usando mscaras que los representan. Por fortuna, las mscaras s son iguales. La Palabra de Configuracin se puede cambiar en el momento de quemar el PIC en el IDE del software de programacin, pero sera mejor que sus valores se cargaran desde el archivo *.hex junto con el cdigo ejecutable. En seguida se muestra un ejemplo de

CURSO_MICROS

P g i n a | 14

cmo establecer los fuses en el cdigo fuente del programa en lenguaje ensamblador, mediante la directiva __config: __config _XT_OSC & _WDT_OFF & _PWRTE_ON Esta lnea pone tres mscaras (_XT_OSC, _WDT_OFF y _PWRTE_ON) enlazadas por el operador &. Cada mscara indica el estado de un fuse. En este caso se indica: adaptar el circuito interno de oscilador para un XTAL, inhabilitar el Watchdog y habilitar el Power-up Timer. El resto de los fuses (los no citados) tendrn sus valores por defecto. No te preocupes si el anterior parrafo se ley extrao. A continuacin se describen los 9 fuses de los PIC16F87xA. El PIC16F84A solo cuenta con los 4 primeros. Tambin se muestran las mscaras correspondientes utilizadas en ensamblador. Fuses de configuracin Seleccin del oscilador Este fuse se representa por los bits Fosc1 y Fosc0. Sirve para adaptar el circuito interno del oscilador segn el componente externo que se usar como fuente del reloj del sistema.

_LP_OSC = Oscilador LP. Se usa cuando el PIC va a trabajar con un cristal de baja potencia. _XT_OSC = Oscilador XT. Se usa cuando el PIC trabajar con un cristal o resonador de frecuencias iguales o menores que 4 MHz. Es la opcin que usaremos a lo largo del curso. _HS_OSC = Oscilador HS. Se usa cuando el PIC operar con un cristal o resonador de alta frecuencia, iguales o mayores que 4 MHz. _RC_OSC = Oscilador RC. Se usa cuando el PIC va a operar con un circuito RC. Es til para sistemas de bajo costo aunque con frecuencias bajas y de menor nivel de estabilidad. Es la opcin por defecto.

El Watchdog El Watchdog o WDT es un temporizador que puede monitorizar el funcionamiento fluido del microcontrolador. El WDT lo estudiaremos al final porque no es imprescindible. De momento diremos que se trata de un temporizador que una vez habilitado debemos resetear peridicamente en el programa. Si no lo hacemos, l resetear el PIC y el programa se volver a ejecutar desde 0. En la mayora de los programas no querremos estar preocupndonos del estado del WDT, as que la mejor decisin ser inhabilitarlo poniendo la mscara _WDT_OFF. Esto es necesario porque el WDT est habilitado por defecto.

_WDT_ON = WDT habilitado. Opcin por defecto. _WDT_OFF = WDT inhabilitado.

Power Up Timer Se representa por el bit PWRTE. El Power-up es un circuito que estando habilitado temporizar cerca de 72 ms despus de conectada la alimentacin del PIC, mantenindolo en estado de reset. Luego de este tiempo el PIC empezar recin a

CURSO_MICROS

P g i n a | 15

ejecutar el programa. Es muy til para esperar a que la tensin de alimentacin se estabilice.

_PWRTE_ON = El temporizador Power-up est habilitado. _PWRTE_OFF = El temporizador Power-up est inhabilitado. Opcin por defecto.

Proteccin de Cdigo Se representa por el bit CP. Este fuse pone proteccin a la memoria de programa. Como sabemos, es posible leer el programa contenido en el PICmicro mediante el dispositivo programador (JDM por ejemplo). Si activamos este fuse, dicha lectura ser impedida. Tambin implica renunciar a la caracterstica de verificacin del programa; as que si lo vas a activar, no olvides reconfigurar el software de programacin.

_CP_OFF = Proteccin de cdigo inhabilitada. Es la opcin por defecto. _CP_ON = Proteccin de cdigo habilitada (PIC16F84A). _CP_ALL = Proteccin de cdigo habilitada (PIC16F87xA).

Escritura en memoria de programa Se representa con los bits WRT1 y WRT0. Los PIC16F87xA ofrecen la posibilidad de escribir en su memoria de programa Flash en tiempo de ejecucin. Sin embargo, esto algunas veces puede resultar riesgoso. As que para dotarle se seguridad adicional al programa este fuse permite configurar qu rea de memoria quedar protegida y qu rea no. El efecto no es siempre el mismo en los PIC16F877A/876A que en los PIC16F874A/F873A: no es lo mismo la mitad de la memoria del PIC16F876A que la mitad de memoria del PIC16F873A.

_WRT_OFF = No hay proteccin de la memoria de programa. Opcin por defecto. _WRT_256 = Se protegen las primeras 256 palabras de la memoria de programa. _WRT_1FOURTH = Se protege el primer cuarto de la memoria de programa. _WRT_HALF = Se protege la primera mitad de la memoria de programa.

Modo ICD Se representa con el bit DEBUG. Con el mdulo ICD (In Circuit Debugger) habilitado, los PIC16F87xA ponen en accin la circuitera interna que monitoriza el estado del CPU, de los perifricos internos y de los registros especiales del PIC. Los resultados sern almacenados en un rea reservada de la memoria RAM y luego enviados a un ordenador a travs de una interface conformada por los pines RB6 y RB7. Del lado del ordenador estar corriendo un programa como el MPLAB en modo ICD para recibir todos los datos e ir visualizndolos en la pantalla. Tambin es posible enviar desde el ordenador comandos de ejecucin del programa como Step into, Step over, etc. (todos estos conceptos sern abarcados a lo largo del curso). En otras palabras, es como correr el simulador MPLAB-SIM o el VSM Proteus pero esto ser real y a veces en tiempo real. La habilitacin del modo ICD implica renunciar a los pines RB6 y RB7 como puertos de E/S convencionales, a un nivel de pila y a algo de memoria RAM.

_DEBUG_OFF = ICD inhabilitado. Opcin por defecto. _DEBUG_ON = ICD habilitado, RB6 y RB7 se destinan al debugger.

CURSO_MICROS

P g i n a | 16

Proteccin de la EEPROM interna Se representa con el bit CPD. Sirve para proteger la memoria EEPROM interna de los PIC16F87xA contra accesos mediante un dispositivo externo. Funciona similar a la proteccin de la memoria de programa, esto es, podr ser accedida normalmente por el CPU en tiempo de ejecucin, mas no, por ejemplo, por un programador de PICs.

_CPD_OFF = EEPROM interna desprotegida. Opcin por defecto. _CPD_ON = EEPROM interna protegida.

Programacin en bajo voltaje Se representa con el bit LVP. Los PIC16F87xA tambin ofrecen la posibilidad de ser programados con una tensin igual a Vdd en el pin MCLR, en vez de los 12 V acostumbrados. ste es el modo de programacin ICSP (In Circuit Serial Programming) de bajo voltaje. Los pines RB6 y RB7 tambin deben ser sacrificados para uso exclusivo del programador y adems se les suma la presencia del pin RB3 como seal del modo de programacin. Dada esta interface, no debe sorprender que esta caracterstica puede ir muy de la mano con el depurador ICD. Sin embargo, para la programacin en bajo voltaje no se requiere de herramientas especializadas. Habilitado este fuse, los pines RB7, RB6 y RB3 no respondern como puertos de E/S tpicos.

_LVP_ON = Programacin en bajo voltaje habilitado. Opcin por defecto. _LVP_OFF = Programacin en bajo voltaje inhabilitado.

Brown Out Reset Es un reset por baja tensin. Esta caracterstica le permite al PIC16F87xA auto resetearse cada vez que detecte una cada de tensin en la alimentacin, por debajo de 4 V (valor tpico). El PIC tiene un filtro pasa-bajas interno que evade el ruido. A tensiones aceptablemente por debajo de este nivel de tensin el PIC todava puede tener una operacin correcta pero no garantizada. Si no se quiere correr el riesgo y se prefiere optar por que el PIC reinicie todo, se puede activar este fuse mediante su bit de configuracin BOREN o mediante su mscara respectiva.

_BODEN_ON = Brown out reset habilitado. Opcin por defecto. _BODEN_OFF = Brown out reset inhabilitado.

Puertos Descripcin de los Puertos Los puertos se conforman por las lneas del microcontrolador donde se pueden conectar los dispositivos de Entrada/Salida a controlar, por ejemplo LEDs, displays, transistores, otros ICs o, mediante rels u optoacopladores, cargas de 110V/220V como medianos motores. El PIC16F84A tiene 2 puertos, representados por los registros PORTA y PORTB. Los PIC16F87xA tienen hasta 5 puertos, accesibles mediante los registros PORTA, PORTB,

CURSO_MICROS

P g i n a | 17

PORTC, PORTD y PORTE. No todos los puertos tienen los 8 pines completos. El puerto E, por ejemplo, solo tiene 3. Los pines pueden tener nombres simples, como RB1, o compuestos, como RC6/TX/CK. Los nombres compuestos implican que dichos pines tienen funciones multiplexadas. Por ejemplo el pin RC6, adems de pin digital convencional puede funcionar como lnea de transmisin TX del puerto serie cuando el USART trabaja en modo asncrono o como lnea de reloj CK cuando el USART trabaja en modo sncrono. En principio todos los pines son bidireccionales cuando actan como lneas de E/S digital. La direccin es configurable por software. Algunos pines pierden esa funcin cuando su control es asumido por algn mdulo relacionado. Cuando actan como salidas, los pines pueden entregar tensiones de hasta Vdd. Cuando actan como entradas pueden manejar niveles de hasta 0.3V por encima de Vdd. El diseo de los pines incluye diodos internos de sujecin que les permiten soportar tensiones mucho mayores que Vdd o inferiores que GND, siempre que la corriente no sobrepase del orden de los micro Ampers. Cada pin de puerto puede suministrar o recibir hasta 25 mA de corriente. Sin embargo, esta capacidad no puede estar presente en todos los pines al mismo tiempo. En seguida tenemos los lmites de corriente total que soporta cada puerto: Para el puerto A del PIC16F84A:

Mxima corriente suministrada = 50 mA Mxima corriente recibida = 80 mA

Para el puerto B del PIC16F84A:


Mxima corriente suministrada = 100 mA Mxima corriente recibida = 150 mA

Para los puertos A, B y E (juntos) de los PIC16F87xA:


Mxima corriente suministrada = 200 mA Mxima corriente recibida = 200 mA

Para los puertos C y D (juntos) de los PIC16F87xA:


Mxima corriente suministrada = 200 mA Mxima corriente recibida = 200 mA

CURSO_MICROS

P g i n a | 18

Diagrama de pines de los PIC16F84A y PIC16F87xA. El puerto B Empezamos con el puerto B porque es el ms fcil. Los nombres sencillos de sus pines revelan que no tienen muchas funciones. Los 8 pines RB0...RB7 trabajan como E/S digitales bidireccionales por igual. Es adecuado para interfaces ordinarias como el manejo de LEDs, control de LCDs, teclados, lectura de switches, pulsadores, etc. La etiqueta INT en el pin RB0/INT significa que adems se puede configurar para recibir interrupciones externas. Solo en ese estado se comporta como entrada Schmitt Trigger. De hecho, los pines RB4, RB5, RB6 y RB7 tambin pueden responder a interrupciones. Estas interrupciones le permiten al microcontrolador atender inmediatamente a los eventos de cambios de nivel lgico en los puertos sin necesidad de estar monitorizndolos constantemente. Las interrupciones se estudian con profundidad mucho ms adelante. El puerto B es el nico entre los PIC16F que incluye resistencias de pull up internas. Eso nos ahorrar el tener que ponerlas externamente cuando sean requeridas, aunque lo ideal sera que hubiera pull-ups en todos los puertos, as como que todos los pines fueran Schmitt Trigger.

CURSO_MICROS

P g i n a | 19

Recordemos (del captulo 2) que los pines RB6 y RB7 tambin sirven de interface para la programacin del PICmicro. Solo en ese modo estos pines actan como Schimitt Trigger, aunque tenga poca relevancia para nuestros propsitos. Tambin recordemos (del captulo 3) que los PIC16F87xA soportan la programacin en bajo voltaje. En ese modo su pin RB3 tiene una funcin muy crucial. Eso explica la inscripcin adicional PGM (Modo Programacin). La programacin en bajo voltaje se habilita/inhabilita va los Bits de Configuracin. El puerto A El PIC16F84A tiene un puerto A de 5 pines, desde RA0 hasta RA4. Los cuatro primeros funcionan normalmente como pines de E/S bidireccionales y tambin soportan niveles TTL. El pin RA4, no obstante, es un tanto especial. Como salida es de drenador abierto y requerir de una resistencia de pull externa si se usa como tal. Es el nico pin que en todo momento tiene buffer Schmitt Trigger, lo que como entrada le da una mejor performance, y el nico pin que puede recibir voltajes hasta de 8.5 V. RA4 puede funcionar adicionalmente como entrada del Timer0 cuando opera en modo Contador. Por eso su etiqueta adicional T0CKI (Timer0 Clock Input). (Descuida, si de momento no entiendes mucho de estos conceptos, esto no se aprende de la noche a la maana.) En los PIC16F87xA el puerto A tiene 6 pines. Los cinco primeros son compatibles con los del PIC16F84A. El pin RA5 tambin es E/S, TTL y bidireccional. Ahora, la gran diferencia en el puerto A de los PIC16F87xA es que est multiplexado para recibir 5 de los 8 canales posibles del conversor ADC (analgico-digital) de estos PICs. Eso es lo que indican las etiquetas ANx que poseen algunos pines. Nota que el pin RA4/T0CKI no est en este grupo. Ahora un punto clave: para confusin de los que migran a los PIC16F87xA el puerto A inicia con una configuracin analgica. Si se quiere usarlo para interface digital ser necesario reconfigurarlo en el registro ADCON1. Ms adelante se muestra cmo. El puerto C Muchos (casi todos) los mdulos o perifricos nuevos de los PIC16F87xA tienen salida al exterior mediante los pines de este puerto. Aqu estn los pines de recepcin RX y transmisin TX del puerto serie del USART; aqu estn los pines de datos SDA y reloj SCL del mdulo MSSP cuando funciona en modo I2C; aqu estn las salidas de los dos canales de PWM CCP1 y CCP2 de los mdulos del mismo nombre cuando generan ondas PWM; aqu estn las entradas del Timer1 T1OSO/T1CKI y T1OSI cuando opera en modo Contador. Creo que no tiene caso seguir con la descripcin porque sera casi interminable y porque nos familiarizaremos con muchos de estos pines cuando estudiemos los mdulos a los que estn relacionados. Por lo dems, los pines del puerto C no necesitan de configuracin previa alguna para trabajar como E/S bidireccionales, compatibles con TTL.

CURSO_MICROS

P g i n a | 20

El puerto D Est conformado por los 8 pines RD0...RD7. Junto con el puerto E solo estn presentes en los PIC16F de 40 pines y tienen la caracterstica destacable (y quiz superflua) de ser la interface del mdulo PSP o Puerto paralelo esclavo. Pero como ese mdulo casi ni se usa, el puerto D podra tranquilamente reemplazar o ayudar al puerto B en las interfaces con los dispositivos fotoelectrnicos, electromecnicos, etc., dado que es tambin es bidireccional, configurable pin por pin y maneja seales TTL. El puerto E Solo tiene 3 pines RE0...RE2, que se pueden usar como analgicos o digitales. Como pines digitales son bidireccionales y tambin pueden funcionar como seales de control RD, WR y CS del mdulo PSP. Como pines analgicos, dan ingreso a 3 de los 8 canales posibles del conversor ADC. Al igual que el puerto A, el puerto E tambin inicia con sus pines como entradas analgicas y habr que configurarlos del mismo modo para que trabajen como pines de E/S digital, si as se desea.

Configuracin de los puertos Despus de un reset todos los puertos inician con sus pines configurados como entradas, pero se pueden reconfigurar en cualquier punto del programa mediante los registros TRISx. Cada puerto tiene su correspondiente registro TRIS, as por ejemplo, TRISA para PORTA, TRISB para PORTB,... y TRISE para PORTE. Si se escribe un 1 en un bit de TRISx, entonces el pin correspondiente en el puerto x ser de entrada y si se le escribe un 0, el pin ser de salida. Para recordar este concepto suele ser til notar la semejanza del 0 con la o (de output) y del 1 con la i (de input).

0 Output = salida 1 Input = entrada

Por ejemplo, si escribimos el valor 00001111 en TRISB, entonces los cuatro pines de menor peso del puerto B sern entradas digitales y los cuatro pines superiores sern salidas. Si escribimos 00000001 en TRISA, nicamente el pin RA0 ser entrada y los dems sern salidas. Como el puerto A no tiene los 8 pines completos, los bits de mayor peso de PORTA y TRISA no tienen significado. La codificacin de lo expuesto sera as: banksel TRISB ; Cambiar al Banco de los registros TRIS movlw b'00001111' ; Mover literal 00001111 a W movwf TRISB ; Mover W a TRISB movlw b'00000001' ; Mover literal 00000001 a W movwf TRISA ; Mover W a TRISA

CURSO_MICROS

P g i n a | 21

Otra forma de configurar los puertos es pin por pin, utilizando las instrucciones bsf (bit set file = setear bit en registro) y bcf (bit clear file = limpiar bit en registro). Por ejemplo. banksel TRISB bsf TRISB, 2 bcf TRISA, 5 ; Cambiar al Banco de los registros TRIS ; Setear bit 2 de TRISB ; Limpiar bit 5 de TRISA

As hemos configurado los pines RB2 para entrada y el pin RA5 para salida. El resto de los bits de los registros TRISA y TRISB conservarn sus valores anteriores. Finalmente, el siguiente fragmento configura los puertos del PIC empleando una forma de cofidicacin alternativa y a veces ms ventajosa. banksel TRISB ; Cambiar al Banco de los TRIS movlw b'00001111' ; Configurar nibble alto de PORTB como salida movwf PORTB ; y el nibble bajo como entrada bsf PORTA, 4 ; Configurar pin RA4 como entrada La pregunta es: acaso los puertos no se configuraban en los registros TRISx? Bueno, sucede que los siete primeros bits de los registros PORTx y TRISx son iguales, de modo que una vez cambiado de banco se pueden llamar por igual. Esto es extensible a todos los registros de la RAM, aunque solo resulta prctico al trabajar con los puertos. Ahora observemos una piedra con la que todos en algn momento hemos tropezado sobre todo al portar nuestros cdigos del PIC16F84A a los PIC16F87xA. Se trata de los puertos A y E. Estos puertos adems de entradas inician configurados como pines analgicos (entradas del ADC). El cdigo del programa parece tan lgico pero el PIC no funcionaba bien porque el puerto A no era digital. Es posible establecer varias combinaciones seleccionando los pines que sern digitales y los que sern analgicos. Por ejemplo, si se quiere que todos los pines sean digitales, hay que escribir el siguiente cdigo en ensamblador. Banksel ADCON1 movlw 0x06 movwf ADCON1 O en lenguaje C ADCON1 = 0x06; // Poner 0x06 en ADCON1 ; Cambiar al banco de ADCON1 ; Mover 0x06 a W ; Mover W a ADCON1

Los cdigos mostrados cargan el registro ADCON1 con el valor 0x06. ADCON1 es uno de los registros que controlan el conversor ADC. Este mdulo lo estudiaremos tendidamente en su momento. Por ahora nos bastar con saber que si escribimos 6 7 en l, todos los pines de los puertos A y E tendrn comportamiento digital de E/S. Las resistencias de Pull-up Una de las cualidades que distinguen a los microcontroladores de los microprocesadores es que encierran en un solo chip todos los elementos posibles de un sistema de control. Con este fin los PICmicros incorporan en su puerto B transistores a manera de fuente de corriente que en la prctica funcionan como resistencias de pull-up.

CURSO_MICROS

P g i n a | 22

Estas pull-ups nos pueden ahorrar el uso resistencias de sujecin externas en los pines del puerto B configurados como entradas. Las pull-ups se podran equiparar con resistencias de 20 K, a calcular por la corriente que dejan fluir. Las 8 pull ups se pueden habilitar al mismo tiempo limpiando el bit NOT_RBPU, del registro OPTION_REG. Las pull ups solo sern efectivas en los pines que actan como entradas; en los pines RBx configurados como salidas las pull ups quedan automticamente inhabilitadas. La siguiente figura muestra la conexin de un pulsador al PIC aprovechando la pull-up del pin RB7. Fjate en que las pull-ups no se pueden usar como resistencias para excitar dispositivos como LEDs, rels, etc.

Ejemplo de uso de las resistencias de pull-up. La figura de ejemplo solo muestra la pull-up del pin RB7 pero estn presentes en todos los pines del puerto B. Los siguientes cdigos habilitan todas las pull-ups del puerto B, aunque solo se activarn en los pines configurados como entradas. banksel OPTION_REG ; Cambiar al banco de OPTION_REG bcf OPTION_REG, NOT_RBPU ; Habilitar pull-ups de PORTB O en lenguaje C OPTION_REG.NOT_RBPU = 0; Tutorial proteus Qu es Proteus VSM? A lo largo de este curso no muestro inters personal para promocionar algn producto en particular. Pero al Csar lo que es del Csar y no tengo mayor reparo en asegurar que Proteus VSM es el mejor software de su clase que existe para aficionados y profesionales dedicados al desarrollo de proyectos con microcontroladores. Proteus ha revolucionado el concepto de simulacin de circuitos electrnicos al pasar de las tpicas presentaciones grficas y textuales de informacin preprocesada (al estilo PSPICE) a la simulacin interactiva muchas veces en tiempo real. // Habilitar pull-ups de PORTB

CURSO_MICROS

P g i n a | 23

Podra seguir listando las potentes caractersticas de Proteus, pero como tampoco pretendo desafiar sus archivos de ayuda y manuales oficiales, los cuales son muy buenos y de lectura recomendada (para quien tenga tiempo:), prefiero aconsejarte que le eches un vistazo a los excelentes ejemplos que trae incluido. Quedars ms que asombrado. El entorno de ISIS Proteus

ISIS es el entorno de trabajo de Proteus. Aqu se disean, dibujan y simulan los circuitos. Bueno, no voy a describir cada uno de los elementos de esta ventana. T mismo puedes ir conocindolos con tan solo sealarlos con el puntero del mouse. Adems de las clsicas sugerencias que suele ofrecer como cualquier otro buen programa de Windows, Proteus muestra una pequea descripcin del elemento apuntado cuando se pulsa la tecla F1. Por ejemplo, la siguente figura muestra la informacin contextual del botn Generator. Claro que est en ingles, pero igual se entiende aunque sea un poquito. Adems, no me digas que no sabes lo que significan esas frasecitas como Copy to clipboard, Save current design, Zoom in, Zoom out, etc. Por favor! En cuanto a los elementos y comandos que son ms propios de Proteus, aprenderemos a usarlos de a poco.

CURSO_MICROS

P g i n a | 24

Se llama por objeto a los diversos elementos que pueden estar presentes en la hoja de diseo. Segn su categora presente en las barras de herramientas, los podemos citar as: Components. Representan todos los componentes electrnicos presentes en un circuito, como los LEDs, condensadores, microcontroladores, etc. Virtual Instruments. Ofrece los instrumentos de medicin como el Osciloscopio, voltmetro, frecuencmentro, etc. Generator, para producir ondas cuadradas, senoidales, de reloj, etc. Simulation Graphs, para visualizar los resultados de la simulacin en grficos, al estilo de los programas como Electronics Workbench o Cadence Orcad. Inter-sheet Terminal. Nos permitir colocar los terminales de entrada, salida, as como las fuentes de alimentacin. Etc. Etc. El panel de la zona izquierda del entorno ISIS, que en la Figura 1-1 se ve con el ttulo de DEVICES y con los botoncitos de P y L al lado, se llama Object Selector. El Object Selector eventualmente cambiar de nombre y desplegar los elementos o formas de la categora seleccionada actualmente (Components, Virtual Instruments, etc.). Dibujando el circuito Durante este captulo vamos a trabajar con la prctica 4-2 del Mdulo 1. Para aadir un objeto a la hoja de diseo primero se establece la categoria del objeto. Por ejemplo para empezar a colocar los componentes electrnicos del circuito debemos seleccionar el icono Components de la barra de herramientas.

CURSO_MICROS

P g i n a | 25

Con ello, la ventanita Object Selector se convierte en Device Selector. En la figura de arriba aparece vaco porque an no hemos sacado ningn componente. As que clicamos ese botoncito P o sencillamente presionamos la tecla P. En cualquier caso nos veremos con la ventana Pick Devices.

En la caja de texto Keywords se escribe el componente buscado. Nota que Proteus tiene un excelente motor de bsqueda y no es necesario escribir el nombre completo de una parte ni usar comodines como en otros programas. A medida que se va escribiendo, Proteus va mostrando todos los elementos coincidentes. En este circuito necesitaremos PIC16F84A, resistor, capacitor, button (pulsador), LED y crystal. Las fuentes de alimentacin se sacan de otro lado. Una vez localizado el componente buscado, le damos doble click para enviarlo al Device Selector. As podemos ir sacando los dispositivos restantes. Cuando hayamos terminado con esta parte cerramos esta ventana dndole al botn Cancel o, mejor an, presionando Escape en el teclado. Ahora el panel Object Selector contiene los diversos dispositivos que formarn nuestro circuito. Seleccinalos uno a uno y luego colocalos en el esquemtico con un click izquierdo.

CURSO_MICROS

P g i n a | 26

Los smbolos de tierra y alimentacin que figuran arriba no se encuentran en las libreras de los dispositivos electrnicos. Los puedes sacar del Object Selector luego de ubicarte en la categora Inter-sheet Terminal, identificada con el icono de la barra de herramientas. De all tambin podemos sacar los terminales de entrada INPUT y salida OUTPUT, muy tiles para conectar elementos sin necesidad de unirlos directamente con cables.

Como los objetos no siempre se presentarn en la posicin deseada, conozcamos algunas formas de cambiarlas. Un objeto queda seleccionado por un click izquierdo del mouse. Con ello aparecer resaltado en color rojo (por defecto). Se quita la seleccin de un objeto haciendo otro click sobre alguna parte vaca de la hoja de diseo. Para editar la posicin de un elemento seleccinalo y abre su men contextual (de click derecho). All encontrars las diversas opciones disponibles, como mover, borrar, rotar

CURSO_MICROS

P g i n a | 27

en un sentido u otro, o reflejar sobre el eje X o Y. Alternativamente, puedes usar otros atajos, por ejemplo:

Para mover un objeto, seleccinalo y luego arrstralo con el botn izquierdo hasta el lugar deseado. Para rotar un objeto, seleccinalo y luego presiona la tecla + o - del teclado numrico tantas veces hasta conseguir la orientacin deseada. Otra forma de hacerlo es aplicando los botones de rotacin al objeto selecionado. La rotacin solo es posible en ngulos rectos.

Para reflejar un objeto, ya sea sobre un eje horizontal o vertical, seleccinalo y utiliza los botones de reflexin de la figura de arriba. Para eliminar un objeto, dale doble click derecho, o seleccinalo y usa la tecla supr.

La seleccin de un grupo de objetos se logra encerrndolos en un rectngulo dibujado con el botn izquierdo. Una vez hecho esto el men contextual del botn derecho desplegar las opciones de edicin de grupo como copiar, mover o rotar/reflejar. Estas opciones tambin estn presentes en la barra de herramientas. Entre todo, la forma ms fcil de mover un grupo de objetos seleccionado es arrastrarlo con el botn izquierdo.

Con frecuencia tras la edicin de los objetos an quedarn restos o marcas de dibujo sobre el esquemtico. Para refrescar de forma rpida todo el diagrama se puede presionar la tecla R (de Redibujar). Los cables para la interconexin de los elementos del circuito no estn en una categora en especial. Se colocan directamente, aprovechando la forma de lpiz del cursor y las marcas de snap (esos pequeos ) que aparecen al acercar el lpiz a algn terminal conectable.

CURSO_MICROS

P g i n a | 28

En este punto vamos a detenernos a comentar los elementos de este diseo porque nos resultarn muy recurridos en el futuro y adems aprovechar para justificar su presencia.

PIC16F84A. A lo mejor el PIC que ves en mi esquemtico no se parece al que tienes en tu Proteus. Lo que importa es que sea o tenga el mismo modelo, el resto es solo cuestin de apariencia (un tema que quizs aprendamos a manejar ms adelante). Tal vez te inquiete la ausencia de los pines GND y VDD, pero inclusive eso no cuenta para la simulacin. De hecho, Proteus asume que, como la mayora de los dispositivos digitales, el PIC trabajar con alimentacin de 0 y 5 V. LED-BLUE, LED-GREEN... Hay varios tipos de diodos LED disponibles. En este caso buscaremos los LEDs animados (los que prenden). Como sta es una simulacin, los colores quedan en segundo plano. RESISTOR. Al escribir resistor en la casilla Keywords se listarn muchsimas partes, desde los modelos genricos hasta los resistores comerciales. En principio, podramos elegir cualquiera de ellas; de todos modos, la impedancia puede reajustarse fcilmente. Tambin, se podra afinar la bsqueda aadiendo el valor de la resistencia. Como ves, yo escog el genrico RESISTOR. CERAMIC22P. Al igual que las resistencias, hay varios modelos de condensadores disponibles y admisibles. Solo que esta vez quise tomar una parte ms especifica y, para no tener que cambiar el valor del capacitor despus, busqu capacitor 22p. Por cierto, stos son los capacitores que estabilizan el circuito oscilador del XTAL. Sin embargo, debes saber que para los microcontroladores, este circuito no tiene directa intervencin en la simulacin. De hecho, la frecuencia de operacin del

CURSO_MICROS

P g i n a | 29

PIC se establece en el mismo PIC, con lo cual el circuito del XTAL quedara de adorno. A veces se lo suele ignorar para aligerar la carga del diseo y mejorar en algo la velocidad de simulacin.

DISC100N16V. Hall este capacitor de desacoplo de 100 nF al escribir capacitor 100n. A decir verdad, ya que en el entorno de ISIS no hay ruido ni interferencias de ese tipo, este capacitor tambin es decorativo. CRYSTAL. Como se dijo anteriormente, en Proteus la frecuencia de trabajo del PIC es establecida en el mismo PIC (leer la siguiente seccin), quedando la presencia del cristal como innecesaria. BUTTON. As se llaman los infaltables pulsadores.

Edicin de las propiedades de los objetos Una de las grandes ventajas de las simulaciones es que se pueden cambiar los elementos del diseo o sus valores con suma facilidad. Para editar las propiedades de un objeto hay que darle doble click izquierdo. As se accede a su ventana de propiedades, la cual, por supuesto, variar de acuerdo con el objeto tratado. Por ejemplo, si quisiramos cambiar el valor de una resistencia, solo habra que modificar el parmetro Resistance en su ventana Edit Component.

La fuente de alimentacin POWER tiene un valor de 5 V por defecto, aunque no se note as en el esquemtico. Si quieres ver que al menos diga VDD o VCC, puedes abrir su ventana de propiedades y escoger una opcin del combobox.

CURSO_MICROS

P g i n a | 30

Claro que el susodicho combobox no ofrece tensiones ordinarias. Si las requiriramos las tendramos que escribir a mano. Por ejemplo, para una fuente de 12V se escribe +12V al lado de String. La V no hace falta, pero el signo al inicio es imprescindible. Adems, no se deben dejar espacios en blanco. La edicin de las propiedades del PIC puede pasar por cambiar muchsimos parmetros. En este momento solo veremos los dos que realmente interesan en la gran mayora de diseos. El programa del PIC y su frecuencia de operacin.

Program File es el campo donde se carga el programa del microcontrolador. ste puede ser el conocido archivo *.HEX (el mismo que utilizamos para grabar el microcontrolador) o un archivo de depuracin (*.COF o similar), que para propsitos de simulacin y depuracin resulta ms ilustrativo. Detallaremos este punto luego. Para cargar un archivo dale click al icono de carpeta que aparece al lado y en el dilogo Select File Name que surgir busca y selecciona el archivo en cuestin. Program Clock Frequency. En el caso de los PIC16 es la frecuencia del XTAL usado por el PIC. Para los PIC18 no necesariamente es as.

CURSO_MICROS

P g i n a | 31

Simulacin del diseo Vamos a emplear los siguientes botones de control:

Play (F12). Iniciar la simulacin. Step (Ctrl + F12). Iniciar/Reiniciar el debugging, si al menos hay un microcontrolador en el diseo. Para el debugging (la depuracin) se necesita algo ms que el archivo *.HEX. Pause (Pause). Pausar la simulacin. Stop (Shift + Pause). Detener la simulacin.

Bueno, sin ms, con el circuito mnimamente armado y con el programa del PIC (*.COF, *.HEX u otro) cargado, corre la simulacin presionando el botn Play. Debera verse as:

CURSO_MICROS

P g i n a | 32

Simulacin en tiempo real!!! Qu diferencia con otros simuladores, verdad?

Simulacin y Depuracin del programa del microcontrolador Con este modo podremos ver la ejecucin del programa paso a paso, ver en diversas ventanas el cambio de los registros y dems datos de la RAM del microcontrolador, el contenido de la memoria EEPROM interna, la evolucin de la Pila, etc. En general, digamos que es parecido al MPLAB SIM de MPLAB pero muchsimo mejor, como lo comprobars. Proteus no solo simula el PIC, sino que lo hace en interaccin con el resto de circuito. Adems, ya no tendrs que ir tras esas inquietas ventanas que se esconden por todas partes. Una simulacin paso a paso requiere de un archivo de depuracin creado por alguna herramienta particular. Para los programas en ensamblador Proteus incluye una utilidad que genera un archivo con extensin .SDI. El linkador del MPLAB tambin puede generar un archivo *.COF (Code Object File), que es algo mejor, aunque no tan potente como los generados por los compiladores de alto nivel. Antes de seguir, te advierto que las versiones algo viejitas de Proteus no admiten el archivo *.COF de MPLAB para programas en ensamblador. Si es tu caso, te aconsejo avanzar a la seccin Depuracin/simulacin con un archivo *.sdi y regresar despus. Con todo esto en mente, puedes cargar en el PIC el archivo *.COF en lugar del archivo *.HEX, si es que no lo tenas as antes. Luego presiona el botn Step o simplemente, las teclas Ctrl + F12 del teclado y voil!: tenemos la ventana PIC CPU Source Code.

CURSO_MICROS

P g i n a | 33

En la parte superior derecha de la ventana PIC CPU Source Code estn los siguentes botones, con nombres ya familiares para nosotros: Sobra decir que es preferible acostumbrarse a las teclas de atajo que los identifican (Las he puesto en azul). Play o Execute (F12). Ejecuta la simulacin de corrido. Step over the current Function/Subroutine (F10). Como el Step over de cualquier otro simulador. Si la instruccin actual es una llamada a una funcin o subrutina, se ejecuta toda de golpe sin entrar en su interior. En el caso de los delays relativamente largos notaremos una breve desaparicin de la ventana de cdigo. Step into the current Function/Subroutine (F11). Tampoco nada nuevo. Si la actual instruccin es una llamada a subrutina o funcin, se entra en su interior. Step out of the current Function/Subroutine. Lo mismo de siempre. Si ya te cansaste de estar dentro de una subrutina o funcin, presiona aqu para salir de ella. Execute until the current cursor position is reached. Lo que dice su nombre: ejecutar el programa hasta alcanzar la posicin actual del cursor. Este botn solo se activa si se apunta a una posicin vlida y diferente de la instruccin actual. Se suele usar para medir el tiempo empleado en ejecutarse el cdigo entre dos puntos del programa. Toggle breakpoint at source line off->on->disabled (F9). Supongo que an recuerdas qu son los breakpoints: puntos del programa donde se pausar la simulacin. Con este botn los pones, quitas o inhabilitas. Tambin se puede usar el doble click.

Ventanas de depuracin Cuando se arranca la simulacin, al final del men Debug aparecen los tems de varias ventanas de depuracin (Simulation Log, Watch Window, etc.) que nos recuerdan al men Debug del MPLAB cuando se inicia el MPLAB SIM. Obviamente Proteus soporta mucho ms que un PIC y este men podr crecer segn el diseo. Excepto PIC CPU Source Code, las dems ventanas siempre deberan estar disponibles, inclusive para una depuracin que solo use el archivo *.hex.

CURSO_MICROS

P g i n a | 34

En la siguiente imagen se aprecian algunas de las ventanas de depuracin, bsicamente relacionadas al PIC. Pierde cuidado si lucen algo diferentes a lo que ves en tu ISIS. Todo ello es fcilmente configurable mediante el men contextual de cada ventana. All podrs cambiar colores, tipos de fuente, formatos numricos de presentacin, campos a visualizar (si es el caso), etc. Es una labor de clicks.

CURSO_MICROS

P g i n a | 35

Simulation Log. Enva un reporte de los detalles de la simulacin. En principio muestra las libreras de simulacin cargadas, la versin del kernel de SPICE utilizada y el tamao del archivo (*.hex, *.cof u otro) cargado. Pero, ms importante, tambin desplegar los errores, mensajes y advertencias de los eventos ocurridos durante la simulacin, normalmente referidos al microcontrolador; por ejemplo, cortocircuitos (contention on net), acceso a un registro no implementado del PIC, desbordamientos de la Pila PIC, etc. As que cuando sientas que algo anda mal con la simulacin no dudes en revisar en primer lugar esta ventana. PIC CPU Source Code. La estuvimos viendo desde el principio. Si hay varios PICs en el diseo, se presentar una ventana de cdigo para cada uno. En realidad, eso pasar con todas las ventanas PIC CPU... PIC CPU Stack. Recordemos que la Pila o Stack es donde se guarda temporalmente el valor del PC (Contador de Programa) cada vez que se llame a una subrutina o se atienda una interrupcin. En este panel podrs ver cmo evoluciona y prever peligros de desbordamiento. En la figura de arriba la Pila tiene la primera posicin ocupada (en rojo). Fjate en que la Pila se llena hacia abajo. Eso se debe a que las Pilas siempre trabajan de cabeza. PIC CPU Registers. Muestra todos los registros SFR del PIC, incluyendo el registro de trabajo W. Tambin muestra la siguiente instruccin a ejecutarse y los valores del Contador de Programa PC y del Puntero de Pila SP (Stack Pointer). PIC CPU Program Memory. Visualiza el cdigo del programa en hexadecimal que se entiende poco o nada. Por eso y por falta de espacio no la puse en la figura de arriba.

CURSO_MICROS

P g i n a | 36

PIC CPU Data Memory. Aqu apreciaremos los valores de todos los registros de la RAM del PIC, incluyendo los SFR. Lo malo es que no es nada fcil distinguirlos. Si de examinar los registros SFR se trata, es preferible ver la ventana PIC CPU Registers y para los GPR la mejor alternativa es la ventana Watch Window. Watch Window. Es como la ventanita homnima del MPLAB. All podemos poner solo los registros de la RAM del microcontrolador que queremos observar.

Uso de la ventana Watch Window Los archivos de depuracin producidos por los compiladores de alto nivel despliegan en una ventana aparte todas las variables utilizadas en el programa. Por desgracia, nuestro archivo *.cof para ensamblador no ofrece esa bondad. Una forma, no muy agradable, de resolver este impase es sacarlas a mano a la ventana Watch Window. ste es el men contextual (del botn derecho) de la Watch Window.

Si elegimos la opcin Add Items (By Name)..., veremos una ventana con todos los registros SFR, los cuales podemos enviar uno a uno a la Watch Window dndoles doble click.

CURSO_MICROS

P g i n a | 37

En cambio, al tomar la opcin Add Items (By Address)... estaremos frente a una ventana ms completa donde tambin podemos encontrar los registros SFR al desplegar el combobox Address. Los registros marcados se envan a la Watch Window presionando el botn Add.

Supongo que habrs notado que las variables ordinarias del programa (registros GPR) no aparecen por ninguna parte, por ejemplo, cont1 y cont2 de nuestro SimpleSeq.asm. No habr ms remedio que ponerlas a mano. Sabiendo que cont1 y cont2 tienen las

CURSO_MICROS

P g i n a | 38

direcciones 0x0C y 0x0D, respectivamente, tipeamos esos datos en los campos Name y Address, como se ve en la figura de arriba y luego clicamos Add. Ya que todos los registros los usamos en forma de bytes (8 bits), el marco Data Type queda con Byte. En otras ocasiones se suelen concatenar varios registros para formar variables de 2, 3, 4 u 8 bytes. El marco Display Format es ms previsible y, por supuesto, depender del dato que representa la variable; por ejemplo, nuestros contadores se vern mejor en decimal sin signo (unsigned integer). De todos modos, estas presentaciones tambin se pueden modificar en la misma Watch Window mediante el men contextual de cada elemento. Nota: En este caso fue fcil deducir las direcciones de las variables. En caso de haber muchas ms o de encontrarse en posiciones de difcil localizacin, podemos abrir el archivo *.lst del proyecto y chequear la parte final. All est una tabla de smbolos que, entre otras cosas, muestra la identificacin de una variable, es decir, su direccin. Uso de instrumentos virtuales El icono Virtual Instruments de la barra de herramientas despliega en el Object Selector la lista disponible, de la cual en este momento solo veremos un par de ellas. No tendra sentido que explicsemos los depuradores SPI DEBUGGER e I2C DEBUGGER porque, imagino, an no tenemos idea de lo que significan. Por otro lado, supongo que tampoco te hace falta leer un tutorial para aprender a usar el voltmetro y el ampermetro. Sobre los dems instrumentos, o son muy poco usados o son igual de sencillos.

Uso del COUNTER TIMER Aunque la barra de estado de ISIS va mostrando el tiempo de simulacin transcurrido, no es la mejor forma de medir los tiempos que toman en ejecutarse algunos segmentos de cdigo. El Timer Counter se puede configurar como cronmetro (Time), frecuencmetro (Frequency) o contador de pulsos (Count) en su ventana de propiedades, pero tambin se puede hacer al vuelo en plena simulacin. De sus tres terminales normalmente el nico que se conecta es el CLK. CE puesto a 0 detiene su operacin y RST reinicia el instrumento.

CURSO_MICROS

P g i n a | 39

Cuando la simulacin/depuracin est en ejecucin dale click izquierdo para que aparezca con ventanita ms grande, como la siguiente.

Observa los cuatro botones (tambin presentes en su ventana de propiedades):


MODE establece la operacin del Counter Timer como cronmetro (Time), contador de pulsos (Count), etc. MANUAL RESET. Es ms cmodo hacer el RESET aqu que desde el pin RST. RESET POLARITY indica si el RESET ser en el flanco de subida o de bajada del botn anterior. GATE POLARITY establece si en modo contador el incremento ser con los pulsos positivos o negativos.

A modo de ejemplo veamos el tiempo exacto que toma la subrutina Pausa de nuestro programa SimpleSeq.asm. No pongo las capturas de pantalla porque creo que ya estoy abusando.

CURSO_MICROS

P g i n a | 40

Luego de colocar un Counter Timer sobre el esquemtico es conveniente regresar a la categora Component (son cosas de Proteus). Iniciamos la simulacin/depuracin y agrandamos el Counter Timer para verlo mejor. Nos ubicamos en cualquiera de las lneas call Pausa y reseteamos el Counter Timer. Ejecutamos toda la subrutina con Step Over. Esta subrutina demor 250002 s = 250.002 ms.

Ahora mediremos el tiempo que transcurre entre las instrucciones de las lneas 23 y 30 del cdigo.

Nos ubicamos en la lnea 23 y reseteamos el Counter Timer. Resaltamos la lnea 30 y empleamos la opcin Execute until the current cursor position is reached. El tiempo medido es 750010s = 750.01ms. Otra forma de resolver este tipo de ejercicios es mediante los breakpoints. Nos ubicamos en la lnea 23 y reseteamos el Counter Timer. Ponemos un breakpoint en la lnea 30. Aparte del botn , tambin se puede hacer con la tecla F9 o, al estilo de MPLAB IDE, con doble click en la lnea en cuestin. Deja que corra el programa (F12). Se detendr por s solo en el breakpoint. Obviamente, el tiempo medido fue el mismo.

Uso del OSCILLOSCOPE Los primeros Osciloscopios de Proteus solo tenan dos canales y presentaban una interface bastante sencilla. Ahora son de cuatro canales, a colores y algo ms completos. Con todo, la compatibilidad con las versiones anteriores se mantiene, y a veces inclusive prefiero las primeras.

CURSO_MICROS

P g i n a | 41

Se puede notar una clara distincin de seis recuadros, cuatro de los cuales corresponden a los cuatro canales (Channel) A, B, C y D, con colores distintos.

Los diales Position desplazan las grficas de cada canal hacia arriba o hacia abajo, en el eje Y. Los diales de tensin fijan la amplitud de voltaje por divisin. Va desde 2mV/div hasta 20V/div. Con la barra deslizadora de cada canal se decide si las seales sern tratadas tales cuales son (DC), solo sus componentes alternas (AC), el nivel de tierra (GND) o si no aparecer (OFF).

Las opciones del recuadro Trigger se pueden emplear para muestrear las seales de manera tal que se tengan grficas ms fluidas sin desfases. En realidad, se usa raramente porque este osciloscopio es ms bien ideal y poca falta ese tipo de calibraciones. En el recuadro Horizontal el dial inferior establece el tiempo base para los canales, va desde 0.5s/div hasta 200ms/div. A modo de ejemplo vamos a simular el circuito de un sencillo amplificador conformado por un opamp. Luego tomaremos las seales de tensin en la entrada y en la salida.

CURSO_MICROS

P g i n a | 42

Luego de una adecuada calibracin de los diales tendremos una imagen as:

Uso de grficos de simulacin Dada la potencia del osciloscopio de Proteus, el uso de grficos para visualizar datos de la simulacin podra parecer redundante. Bastar con revisar los ejemplos incluidos en Proteus para darse cuenta de que eso no es del todo cierto. Los grficos de Proteus son muy parecidos a los obtenidos por programas como PSPICE o Electronics Workbench. De hecho, utilizan los mismos modelos de SPICE y el mecanismo para construirlos suele ser similar, sobre todo en los grficos analgicos. Sucede que casi todos los modelos digitales de Proteus son nativos.

CURSO_MICROS

P g i n a | 43

Vamos previendo por tanto que podemos optar por grficos analgicos o digitales, aunque eso no significa que un grfico analgico no pueda incluir una seal digital o viceversa. Continuando con el diseo anterior en este ejercicio vamos a capturar las grficas de las tensiones de entrada y salida del amplificador. Primeramente selecciona la categora Simulation Graph de la barra de herramientas.

Del Object Selector escoge ANALOGUE y luego dibuja un rectngulo en el esquemtico con el botn izquierdo del mouse. Quedar como el que aparece a la derecha de la siguiente figura. (Las flechitas azules Vi y Vo del circuito las pondremos despus.)

Abramos la ventana de propiedades de este objeto para ver un par de detalles. Start time y Stop time determinan el margen temporal de la grfica presentada. As, con un Start time=0 y Stop time=10 tendremos la grfica de los primeros 10 segundos. Cambiando Start time=100m y Stop time=200m establecemos una simulacin entre los tiempos 100ms y 200ms. Se entiende?

CURSO_MICROS

P g i n a | 44

Selecciona la categora Voltage Probe (sondas de voltaje) de la barra de herramientas y sin darle importancia al Object Selector haz click sobre los puntos del circuito cuyas tensiones tomaremos. Aparecern esas pequeas flechitas azules que se conocen como Probes (sondas). Yo solo he puesto dos probes: uno en la entrada y otro en la salida del circuito. Puedes editar las propiedades de los probes como cualquier otro objeto. Como ves en la Figura 1-27, yo les cambi de nombre: Vi para el primero y Vo para el segundo. Es solo para darles algo de identidad. Ahora selecciona un probe y con el botn izquierdo arrstralo y sultalo sobre el rectngulo de la grfica. Si el probe cay bien, debi haber aparecido uno similar dentro del rectngulo. Al final vers que el probe original no se ha movido (si lo hizo, es que fallaste ;). Luego haz lo mismo con el otro probe. El rectngulo quedar como ste.

Ahora presiona la barra espaciadora del teclado. Momentneamente la barra de estado de ISIS mostrar el progreso de la simulacin y cuando haya finalizado, veremos la grfica esperada.

Bueno, en realidad no tanto. Esperbamos ver ondas senoidales, cierto? Debe ser que estn tan juntas que no se notan. As que dale click a la franjita verde superior, all donde dice ANALOGUE ANALYSIS. La grfica aparecer en una ventana propia. (Tambin se puede maximizar desde el men contextual.)

CURSO_MICROS

P g i n a | 45

Bueno, esto tampoco es lo que esperbamos, pero al menos ya tenemos disponibles los botones de ZOOM en la parte baja. Puedes utilizarlos para estirar la imagen. En todo caso, hubiramos establecido la simulacin de un tramo ms corto, verdad? Pues no tenemos que regresar al esquemtico. Se puede reconfigurar el tiempo de simulacin con la opcin Edit Graph... del men Graph. Se volver a presentar la misma ventana de la Figura 1-28, donde se establece el tramo de simulacin. En esta ocasin vamos a poner, Start time=100m y Stop time=110m. Luego presionamos el botn Run simulation for current graph (se del hombrecito rojo corriendo) y...

CURSO_MICROS

P g i n a | 46

Eso s se ve mucho mejor. Nota que los mrgenes de la grfica dicen 100 ms y 110 ms, tal cual lo indicamos. Los dems detalles de la grfica, como el uso del cursor, las coordenadas de tiempo y valores de seal en cada punto mostrados en zona inferior, cambio de colores, etc., los dejo a tu observacin. Solo es cuestin de echarle ojo. Depuracin/simulacin con un archivo *.sdi El archivo *.COF producido por el MPLAB IDE para programas en ensamblador con fines de depuracin sofware solo es soportado por las versiones ms recientes de Proteus. Por estas limitaciones Proteus sola emplear el archivo *.HEX acompaado de un archivo *.SDI, el cual estructuralmente es muy parecido al archivo *.LST. A diferencia de un *.cof, que separa los archivos del programa en diferentes campos, un archivo *.sdi lo presenta todo junto en un solo cuerpo. Esto puede resultar algo incmodo, ya que si trabajamos con varios archivos es precisamente para ordenar las cosas. As mismo, cada vez que se edite el archivo principal del cdigo fuente, Proteus detectar la desactualizacin del archivo *.sdi y ensamblar todo el programa de nuevo. Esto tambin le quita algo de sentido al trabajo del proyecto con el MPLAB IDE. En este modo el nico archivo a cargar en el PIC es el *.HEX; el archivo *sdi solo tiene que acompaarlo en su carpeta de destino. Para crear un archivo *.sdi sigue los siguientes pasos: (Retomaremos nuestro SimpleSeq.asm.)

Ve al men Source y selecciona la opcin Add/Remove Source Files... Te aparecer la siguiente ventana (pero con los campos vacos).

CURSO_MICROS

P g i n a | 47

Ahora dale al botn New y a continuacin selecciona el archivo de cdigo fuente del programa. En caso de tener un programa comprendido por varios archivos, solo se debe seleccionar el archivo principal. (El archivo principal suele tener la extensin .asm y los dems, .inc.) El nombre del archivo seleccionado ir al combobox Source Code Filename. Como ves arriba, yo he escogido el SimpleSeq.asm. Seguidamente, en el combobox Code Generator Tool, elige MPASMWIN. Es una copia del ensamblador que incluye el MPLAB. Con esto vers despus que no har falta construir el proyecto en el MPLAB IDE, dado que se volver a ensamblar todo el programa otra vez. Luego clica OK y sigamos. Nota que en el men Source ahora se ha agregado un tem con el nombre de nuestro archivo *.asm. Seleccionando esa opcin se abrir un editor muy parecido al block de notas con el cdigo del programa. Siendo un editor muy pobre, lo puedes cambiar por otro (Notepad++, por ejemplo) seleccionando la opcin Setup External Text Editor... Prubalo en otro momento, cuando tengas tiempo.

Bueno, terminemos con esto: clica Build All (Construir todo). Por un momento aparecer la clsica ventanita del MPASMWIN y luego quedar una ventana ms grande con el mensaje de Build completed OK al final. Cirrala y luego nos vemos. Nota: si la construccin falla, esta ventana nos dar los errores hallados, pero como no es una lista muy detallada, siempre convendr revisar el archivo *.err.

CURSO_MICROS

P g i n a | 48

Si te fijas en la carpeta del proyecto, vers que ha aparecido (junto a los esperados *.hex, *.lst...) un archivo con extensin .sdi. Solo djalo all. T sigue cargando el archivo *.HEX en la ventana de propiedades del PIC. Ya puedes presionar Ctrl + F12.

CURSO_MICROS

P g i n a | 49

Propiedades avanzadas del PIC Por lo general y como se ha podido comprobar, muchos de los componentes de Proteus no requieren nada de edicin. De hecho, se pueden encontrar partes con modelos que ya tienen todos los parmetros correctamente calibrados. No obstante, hay casos en los que una parte enteramente modelada ms que ayudar puede devenir en una mella para la performance de la simulacin. En este apartado trataremos algunas de las propiedades del PIC cuya configuracin sera ms conveniente que la hiciera el usuario directamente si as lo demandase. La siguiente ventana es la misma que abrimos para cargar el programa del PIC y poner su frecuencia de trabajo. Ahora la tenemos para observar los campos ms interesantes de los que sobran, casi todas pertenecientes al combobox Advanced Propierties.

Program Configuration Word: La palabra de configuracin contiene los Bits de Configuracin. Aunque no se reflejen directamente, sus valores se cargan desde el mismo archivo *.HEX, por lo que este campo no debera quitarnos la atencin. Aun as, en Proteus hace falta algo ms para que los bits de configuracin entren en la simulacin. Sigo hablando de esto en lo subsiguiente. Randomize Program Memory?: Raramente el programa del PIC alcanzar el lmite de la memoria de programa. Las posiciones restantes normalmente quedan como datos 3FFF y as se cargarn para la simulacin. Si por algn extrao motivo quisieras que esas posiciones se rellenaran con valores aleatorios, podras establecer esta propiedad con un YES. Randomize Data Memory?: Igual que el caso anterior pero con la memoria de datos. Generalmente las variables utilizadas en un programa deberan estar inicializadas. As que esta propiedad tampoco debera interesar. Dump Configuration Word? ?

CURSO_MICROS

P g i n a | 50

Model PIC Start-up Delays?: Se refiere al retardo inicial producido principalmente por el temporizador Power-up Timer. Es un tiempo que dura 72 ms tras conectarse la alimentacin del PIC. Durante este lapso el PIC se mantiene en estado de RESET (para que la alimentacin del circuito se estabilice) y luego recin ejecutar la primera instruccin del programa. Es el mismo tiempo que siempre habilitamos con el fuse _PWRTE_ON en la palabra de configuracin. En conclusin, si queremos simular el tiempo del Power-up Timer en Proteus, adems de activar el fuse en el cdigo del programa, debemos poner un YES en este campo. Model PIC Wake-up Delays?: Se trata de un tiempo de 1024 ciclos de reloj (256 s para un XTAL de 4MHz) generado por el temporizador interno Oscillator Startup Timer. Tambin complementa al Power-up Timer pero sirve bsicamente para esperar que el reloj del sistema se estabilice luego de salir del modo Sleep. En el chip real este temporizador no depende de nosotros ya que siempre estar activado. Es solo un tema de Proteus y la verdad es que, como 1024 ciclos son poco apreciables, se suele ignorar para las simulaciones. (Se estudia el modo Sleep en el Mdulo 2.) Generate Q Clocks on CLKOUT Pin?: Cuando el PIC opera con un oscilador RC externo (de frecuencia Fosc) en vez de un XTAL, el pin OSC2/CLKOUT sacar una onda cuadrada de frecuencia Fosc/4. Y nosotros sabemos que a Proteus le interesa un bledo si se usa XTAL, circuito RC u otro oscilador externo. Por tanto, poniendo YES en este campo el pin OSC2/CLKOUT mostrar la onda de Fosc/4 (siendo este Fosc la frecuencia configurada en la ventana de propiedades del PIC), independientemente del oscilador externo usado. Como sea, una seal de este orden en Proteus sera demasiado pesada de simular, por lo que es preferible dejarla tal como est, inhabilitada. Watchdog Timer Period?: El Watchdog es un temporizador que tiene un tiempo base de 18ms, el cual vara ligeramente con la temperatura del chip. Al igual que todas las partes digitales de Proteus, el PIC no tiene un parmetro que interacte directamente con la temperatura establecida para el diseo. Si se desea una simulacin con un valor de Watchdog Timer Period un poquito diferente del tpico, ste es el campo donde se cambia. (Se estudia el Watchdog en el Mdulo 2.) Port Pin Low-High Delay y Port Pin High-Low Delay: En el mundo real los niveles de tensin en los pines del PIC no permutan instantneamente. Estos tiempos de propagacin son del orden de los nanosegundos y Proteus suele despreciarlos por defecto para la simulacin. En caso de tener algn diseo donde ese parmetro realmente te importe debes indicar en este campo los tiempos pertinentes. (Los puedes hallar en el datasheet.) Data EEPROM Write Delay. Segn su datasheet, los PIC16F tienen una EEPROM cuyos datos se graban en un tiempo tpico de 4 ms, llegando a 10 ms como mucho. En este campo se puede establecer algn valor en particular. (Estudiaremos este recurso en el Mdulo 2.) Initial contents of EEPROM. Es la misma EEPROM del punto anterior. Se supone que el contenido inicial de esta memoria tambin puede formar del archivo *.hex; as que este campo ser raramente cargado.

CURSO_MICROS

P g i n a | 51

Mdulo 2 Contenido ndice Captulo 1: El Lenguaje C para microcontroladores

Introduccin
o o o o

Compilador de alto nivel Por qu C y no Basic? Qu compilador usar? nimo! No es tan difcil

Estructura de un programa
o o o o o

Los comentarios Las sentencias Los bloques Las directivas Las funciones

Variables y Tipos de Datos


o o

Declaracin de variables Especificadores de tipo de datos

Sentencias selectivas
o o o o

La sentencia if La sentencia if - else La sentencia if - else - if escalonada La sentencia switch

Sentencias iterativas
o o o

La sentencia while La sentencia do - while La sentencia for

Bloques simples Los operadores

CURSO_MICROS
o o o o o o

P g i n a | 52

Operadores aritmticos Operadores de bits Operadores relacionales Operadores lgicos Composicin de operadores Precedencia de operadores

Las funciones
o o o o o o o

Funciones sin parmetros Funciones con parmetros (por valor) Parmetros por referencia Prototipos de funciones Variables locales y variables globales Variables static Variables volatile

Arrays y Punteros
o o o o o o o o o o o

Los arrays o matrices Declaracin de arrays Inicializacin de arrays Cadenas de texto Los punteros Declaracin de punteros Apuntando a variables Asignaciones indirectas mediante punteros Punteros y arrays Paso de punteros y arrays a funciones Arrays constantes

Captulo 2 El Compilador BoostC


El compilador BoostC/C++ Prctica 1 Creacin de un proyecto en BoostC

CURSO_MICROS

P g i n a | 53

Prctica 2 Prctica 3 Prctica 4

Captulo 3 El Display LCD


Introduccin Pines del LCD Memorias del LCD


o o o o

CGROM DDRAM CGRAM El Puntero de RAM

Set instrucciones del LCD Inicializacin del LCD Interface de un LCD Control del Display LCD Prctica 1 Hellow World Prctica 2 Visualizacin de nmeros Prctica 3 Caracteres grficos en LCD

Captulo 4 El Teclado Matricial


Operacin del teclado Control de un teclado Prctica 1 Lectura de teclado mediante sondeo
o o o

El circuito El cdigo fuente Descripcin del cdigo

Prctica 2 Control de teclado por interrupciones


o o

El cdigo fuente Descripcin del cdigo

CURSO_MICROS

P g i n a | 54

Captulo 5 Las Interrupciones


Introduccin Qu es una Interrupcin? Fuentes de interrupcin Control de interrupciones


o

El registro INTCON

La Interrupcin RB0/INT
o

El registro OPTION_REG

Prctica 1 Interrupcin RB0/INT El Modo Sleep Prctica 2 Durmiendo La Interrupcin de Cambio de PORTB Prctica 3 Interrupcin de Cambio de PORTB Prctica 4 Interrupciones mltiples Anatoma de una Interrupcin
o o o

Secuencia de ejecucin de las interrupciones Retorno de una interrupcin Guardar el Contexto de las Interrupciones

Prctica 5 Interrupciones en ensamblador

Captulo 6 Los Timers


Introduccin El Timer0
o o o o

El registro OPTION_REG El Prescaler del Timer0 El Timer0 en modo Temporizador Clculo de la temporizacin

Prctica 1 Temporizacin con sondeo del Timer0 Interrupcin del Timer0 Prctica 2 Uso de la interrupcin del Timer0

CURSO_MICROS

P g i n a | 55

Prctica 3 Estirando la temporizacin


o

El Timer0 en modo Contador

Prctica 4 El Timer0 en modo Contador El Timer2


o o o o

Registros del Timer2 El Prescaler y el Postscaler del Timer2 Interrupcin del Timer2 Clculo de la temporizacin

Prctica 5 Uso del Timer2 El Timer1


o o o o o

Registros del Timer1 Modos de operacin del Timer1 Interrupcin del Timer1 Clculo de la temporizacin Acceso a los registros del Timer1

Prctica 6 El Timer1 en modo temporizador Prctica 8 El Timer1 con XTAL externo

Captulo 7 El USART

Introduccin El Estndar RS-232


o o o o o

Voltajes de los niveles lgicos del RS-232 Formato de transferencia de datos Velocidad de transmisin (Baud Rate) Seales del puerto serie Control del flujo de datos (Handshaking)

Interface serial microcontrolador-ordenador


o o

Requerimientos hardware Requerimientos software

El USART de los PICs

CURSO_MICROS
o o o o o o

P g i n a | 56

Los registros del USART Inicializacin del USART Transmisin de datos del USART Recepcin de datos del USART Clculo del Baud Rate Los registros TXSTA y RCSTA en detalle

Prctica 1: Comunicacin PC - PIC - LCD El programa Tera Term Prctica 2: Tratamiendo de textos y nmeros Las Interrupciones del USART Prctica 3: Interrupciones del USART El buffer circular o buffer de anillo Prctica 4: Buffer circular con Interrupciones Prctica 5: Interface RS-232 entre dos PICs

Captulo 8 El MSSP en modo I2C

Protocolo del bus I2C


o o o o o o

Caractersticas bus I2C Transferencias de datos Condicines de Start, Stop y Start Repetido El bit de Reconocimiento (ACK o NACK) El Byte de Control Velocidad de transferencia de datos

Mdulo MSSP en modo I2C


o o o o o o

Registros del MSSP en I2C Master mode Configuracin del I2C Master mode Velocidad de transferencias de datos Condicin Start Condicin Repeated Start Condicin Stop

CURSO_MICROS
o o o o

P g i n a | 57

Transmitir Dato y Recibir bit ACK/NACK Recibir Dato y Transmitir bit ACK/NACK Los registros SSPSTAT, SSPCON y SSPCON2 Interrupciones del mdulo I2C

Memorias EEPROM 24xx128


o o o

Descripcin de pines Direccin del dispositivo Lectura y escritura aleatorias de bytes

Prctica 1 Acceso aleatorio a la eeprom 24xx128


o

Lectura Secuencial y Escritura por Pginas

Prctica 2 Acceso secuencial a la 24xx128

Captulo 9 El Conversor Analgico digital


Introduccin Conceptos bsicos


o o

Resolucin y tensiones de referencia del ADC ADC de aproximaciones sucesivas

El mdulo ADC de los PIC16F87xA


o o o o o

Registros del mdulo ADC Configuracin de los canales del ADC Reloj del ADC y Tiempo de Conversin Resultado de la conversin Tiempo de adquisicin

Prctica 1 Uso del conversor ADC


o

Interrupcin del ADC y conversiones en modo Sleep

Prctica 2 Conversiones en modo Sleep

Chapter 10 Los mdulos CCPx en modo PWM


Introduccin Registros de los mdulos CCPx Generacin de ondas PWM de 8 bits

CURSO_MICROS
o o

P g i n a | 58

Periodo y frecuencia y de la onda PWM Duty cycle de la onda PWM

Prctica 1 PWM de 8 bits Generacin de ondas PWM de 10 bits Prctica 2 PWM de 10 bits

1-

El lenguaje C para microcontroladores > Los compiladores de alto nivel

Introduccin Los compiladores de alto nivel Los compiladores de alto nivel son las herramientas de programacin mas potentes que existen. En resumidas cuentas, el compilador traducir el programa escrito en su lenguaje (C, Basic, Pascal u otro) en cdigo ensamblador y luego generar los archivos de depuracin (*.dbg, *.cof, .d31, etc.) necesarios y de ejecucin (.hex). Al trabajar con un lenguaje de alto nivel el programador ya no tiene que preocuparse (o lo hace muy poco) por las caractersticas hardware ni por el ensamblador nativo de cada microcontrolador. Esto simplifica de manera asombrosa el desarrrollo de proyectos. Los compiladores se encargan de traducir el cdigo fuente al cdigo objeto de cada microcontrolador sin inportar mucho cul sea. Por ejemplo, un cdigo escrito para un PIC16F84 podra ser facilmente compilado para un PIC16F877A u otro, y viceversa. Inclusive es posible adaptar un cdigo para un microcontrolador de otra marca, por ejemplo, de Freescale o Atmel. Eso se llama portabilidad. Por qu C y no Basic? Ciertamente, el Basic es el lenguaje ms fcil de aprender (no es exactamente la razn de su nombre). Y aunque los programadores en C de ordenadores miren con desdn a los que usan el Basic, en el mundo de los microcontroladores los compiladores Basic no tienen motivo para sentirse menos. De hecho, algunos pueden ser casi tan eficientes como los mejores compiladores C. Las caractersticas (muchas veces complejas) del C fueron ideadas para el trabajo con sofisticados proyectos, propios de los ordenadores. Muchas de esas caractersticas ya no resultan tan ventajosas en el limitado hardware de los microcontroladors y se convierten en prescindibles. Adems, la simplicidad de los compiladores Basic para microcontroladores tambin permite que varios de ellos, como MBasic o PIC Basic Pro (por citar algunos) mantengan una compatibilidad entre sus cdigos que no se encuentra entre los compiladores C. sas podran ser razones ms que convincentes para empezar por el Basic y, de hecho, es la opcin que muchos han elegido. Por qu nosotros no?

CURSO_MICROS

P g i n a | 59

Porque es verdad comprobable que los mejores programadores trabajan en C (no siempre exclusivamente, pero lo manejan). Por consiguiente, los proyectos ms fantsticos y alucinantes que se pueden encontrar estn en C. Es ms, la mayora de, por no decir todos, los programadores de Basic tarde o temprano se ven obligados a aprender el C. No s t, pero yo opino que esa razn pesa ms. Adems, dada la robustez y la aceptacin del lenguaje C, se lo ha tomado como referencia para lenguajes de otros propsitos como Java, JavaScript, php o de Matlab, entre otros. As que, el C podr servirte para trabajar en otros campos. El programador de C podra, inclusive, aprender luego el Basic sin el menor esfuerzo; lo contrario no es cierto. Qu compilador C utilizar? No quiero burlarme de nadie, pero una vez le en Internet el comentario de un novato: Quiero programar microcontroladores en C. Ya descargu el Visual C++. Qu ms necesito? :). Aparte del lenguaje, nada tiene que ver un compilador para ordenadores con los compiladores para Cs. Poco tiene que ver un compilador para PICs que otro para otros Cs. Inclusive, poco tiene que ver un compilador de PICs de una compaa con otro de otra compaa. Veamos grosso modo algunos aspectos de los compiladores de PICs ms conocidos. Hi-tech C. Es uno de los compiladores producidos por la empresa htsoft. Es quiz el ms eficiente y el que mejor soporta el lenguaje C estndar. Su entorno IDE tambin incluye el mejor depurador ICD. Como contraparte, su apego al hardware del C le resta algo de portabilidad. Tampoco luce libreras incorporadas como otros productos. Pero su principal desventaja es su elevado precio. Y, por si fuera poco, el compilador para la familia de partes PIC18 se vende por separado. IAR C. Los compiladores C de la compaa iar systems tienen bsicamente las mismas caractersticas mencionadas de los compiladores de htsoft, incluyendo sus propios depuradores. As mismo, las versiones para los PIC16 y PIC18 se distribuyen por separado. Actualmente, no s por qu, ya no est disponible la primera versin. CCS C. La empresa ccsinfo decidi dotar a sus compiladores C una capa extra que asla al programador de los recursos intrnsecos del C. Esto puede afectar la portabilidad de sus cdigos a otros compiladores, pero resulta inmejorable, si solo se trabaja en el lenguaje de CCS C, para transportar los cdigos de un PIC a otro (de cualquier familia) con un esfuerzo sin comparacin. Adems, incluye en un solo paquete los compiladores para los PICs de las familias Baseline, Midrange (PIC16 bsicamente) y High performance (PIC18). Al igual que los softwares anteriores, sus libreras estndar, como stdlib.h, stdio.h, string.h y math.h, son muy completas y potentes; pero CCS C supera a sus rivales al incorporar libreras para controlar todos los mdulos internos del PIC y tambin muchsimos dispositivos externos. Mikro C. La compaa Mikroelektronika vende compiladores para PICs en los lenguajes C (MikroC), Basic (MikroBasic) y Pascal (MikroPascal). Yo dira que el estilo de Mikro C se parece al de Hi-tech C y sus facilidades tratan de acercarse a las de CCS C: aunque en muchos casos an es necesario acceder a los

CURSO_MICROS

P g i n a | 60

registros internos del PIC, cuenta con libreras para controlar sus mdulos internos. Tambin tiene una apreciable cantidad de libreras para interfacear dispositivos externos. Lo malo es que todas ellas estn precompiladas y no se podran modificar, en caso de ser necesario. Mikroelektronika y CCS tambin comercializan sus propias tarjetas de entrenamiento para el aprendizaje de sus productos. Para ms informacin puedes visitar sus sitios web. MPLAB C18. Excelente compilador desarrollado por los ingenieros de Microchip. No es gratuito como el MPLAB, pero creo que es el que ofrece la versin demo ms generosa: es 100 % funcional por 60 das. Lamentablemente, como sugiere su nombre, solo trabaja con las partes PIC18. Quiz lo probemos en otro momento. Otros. An hay otros compiladores C (como Bytecraft, BoostC y FedC) que algo menos reconocidos como los anteriores, lo que no significa que sean malos. Tambin he visto algunos de cdigo abierto, pero no son buenos: la gente del GNU trabaja ms con el AVR GCC, un Seor Compilador. Es uno de los pocos casos donde el software libre supera a los comerciales. Como se puede entrever, est orientado a los microcontroladores AVR, de Atmel. Es, adems, el compilador ms difcil de todos; por eso lo estudiaremos en el Mdulo 4. En cuanto a cul compilador usar: la idea de este curso no es aprender a programar con un compilador en particular, y tampoco pretendo promocionar alguno. Despus de todo, una victoria depende ms de la habilidad guerrero que de su espada. He visto super programas hechos con el compilador ms modesto. En este Mdulo 2 uso BoostC porque es muy fcil, porque nos permitir ver ms de cerca cmo funcionan las cosas dentro del PIC y, sobre todo, porque el salto de l a otros compiladores ser mucho ms fcil que hacerlo al revs. En el Mdulo 3 migraremos al CCS C (que adems del lenguaje C usa su propio argot) y en el Mdulo 4 trabajaremos especialmente con AVR GCC. nimo! No es tan difcil Pienso que, comparado con el Basic para microcontroladores, el C es infinitamente ms difcil de aprender. Quienes lo usan, en gran parte, son personas que han tenido experiencia programando ordenadores, personas que han estudiado ms de un libro para dominarlo. Es, literalmente, como aprender un nuevo idioma, y eso no es algo que se hace de la noche a la maana. Eso no suena muy alentador? Para simplificar las cosas, en este captulo no voy a exponer todas las reglas del lenguaje C, aunque s la mayora; digamos el 95 % de lo necesario. El resto: o es solo aplicable a los PCs, o son temas raros o que difieren demasiado entre de compilador a otro y conviene ms revisarlos en sus respectivos manuales. Tambin, y para ahorrar los ejemplos prcticos, asumo que no eres un novato cualquiera, asumo que conoces algo de programacin (aunque sea en ensamblador), que sabes cmo usar las subrutinas, que sabes cmo emplear los bucles, que sabes lo que significa redirigir el flujo de un programa, que sabes para qu sirven las variables, etc. Si no, estars algo perdido.

CURSO_MICROS

P g i n a | 61

Finalmente, no es necesario que te aprendas de golpe todo el captulo; bastar con que lo leas fluidamente una primera vez y regresar luego a consultar algunos puntos de duda. La parte ms complicada es Arrays y Punteros, sobre todo los punteros. As que, ten paciencia con ellos. Estructura de un programa en C Tomaremos en cuenta este sencillsimo ejemplo, escrito para el compilador Hitech PICC. #include <pic.h> // Incluir este archivo /* La siguiente directiva establece la Palabra de Configuracin */ __CONFIG ( PWRTEN & WDTDIS & XT & UNPROTECT ); void pausa(void) { // Llave de apertura del bloque de pausa unsigned int c; // Declarar variable c (de 16 bits) for(c=0; c<60000; c++) { // Llave de apertura del bloque de for /* este bloque est vaco, solo cuenta c desde 0 hasta 59999 */ } // Llave de cierre del bloque de for } // Llave de cierre del bloque de pausa void main(void) { // Llave de apertura del bloque de main TRISB0 = 0; // Configurar pin RB0 como salida while(1) // Bucle infinito { // Llave de apertura del bloque de while RB0 = 1; // Setear bit RB0 pausa(); // Llamar funcin pausa RB0 = 0; // Limpiar bit RB0 pausa(); // Llamar funcin pausa } // Llave de cierre del bloque de while } // Llave de cierre del bloque de main No hay que ser muy perspicaz para descubrir lo que hace este programa: configura el pin RB0 como salida y luego lo setea y lo limpia tras pausas. Es como hacer parpadear un LED conectado al pin RB0. Parpadea porque el bloque de while se ejecuta cclicamente. Los elementos ms notables de un programa en C son las sentencias, las funciones, las directivas, los comentarios y los bloques. A continuacin, una breve descripcin de ellos. Los comentarios Los comentarios tienen el mismo propsito que en ensamblador: documentar y adornar el cdigo. Es todo es texto que sigue a las barritas // y todo lo que est entre los signos /* y */. Se identifican fcilmente porque suelen aparecer en color verde.

CURSO_MICROS

P g i n a | 62

Ejemplos. // ste es un comentario simple /* sta es una forma de comentar varias lneas a la vez. Sirve mucho para enmascarar bloques de cdigo. */ Las sentencias Un programa en C, en lugar de instrucciones, se ejecuta por sentencias. Una sentencia es algo as como una mega instruccin, que hace lo que varias instrucciones del ensamblador. Salvo casos particulares, donde su uso es opcional, una sentencia debe finalizar con un punto y coma (;). As que tambin podemos entender que los ; sirven para separar las sentencias. Alguna vez le que el compilador C lee el cdigo como si lo absorbiera con una caita, lnea por lnea, una a continuacin de otra (evadiendo los comentarios por supuesto). Por ejemplo, la funcin main del programa de arriba bien pudo escribirse del siguiente modo. void main(void) { TRISB0=0; while(1) { RB0=1; pausa(); RB0=0; pausa(); } } Sorprendido? Podrs deducir que los espacios y las tabulaciones solo sirven para darle un aspecto ordenado al cdigo. Es una buena prctica de programacin aprender a acomodarlas. Las sentencias se pueden clasificar en sentencias de asignacin, sentencias selectivas, sentencias iterativas, de llamadas de funcin, etc. Las describiremos ms adelante. Los bloques Un bloque establece y delimita el cuerpo de las funciones y algunas sentencias mediante llaves ({}). Como ves en el ejemplo de arriba, las funciones main y pausa tienen sus bloques, as como los bucles while y for. Creo que exager con los comentarios, pero sirven para mostrarnos dnde empieza y termina cada bloque. Podrs ver cmo las tabulaciones ayudan a distinguir unos bloques de otros. Afortunadamente, los editores de los buenos compiladores C pueden resaltar cules son las llaves de inicio y de cierre de cada bloque. Te ser fcil acostumbrarte a usarlas. Las directivas Son conocidas en el lenguaje C como directivas de preprocesador, de preprocesador porque son evaluadas antes de compilar el programa. Como pasaba en el ensamblador, las directivas por s mismas no son cdigo ejecutable. Suelen ser indicaciones sobre cmo se compilar el cdigo. Entre las pocas directivas del C estndar que tambin son soportadas por los compiladores C para PICs estn #include (para incluir archivos, parecido al assembler),

CURSO_MICROS

P g i n a | 63

#define (mejor que el #define del ensamblador) y las #if, #elif, #endif y similares. Fuera de ellas, cada compilador maneja sus propias directivas y sern tratadas por separado. Las funciones Si un programa en ensamblador se puede dividir en varias subrutinas para su mejor estructuracion, un programa en C se puede componer de funciones. Por supuesto que las fuciones son muchsimo ms potentes y, por cierto, algo ms complejas de aprender. Por eso ni siquiera el gran espacio que se les dedica ms adelante es suficiente para abarcarlas. Pero, no te preocupes, aprenderemos de a poco. En un programa en C puede haber las funciones que sean posibles, pero la nunca debe faltar la funcin principal, llamada main. Donde quiera que se encuentre, la funcin main siempre ser la primera en ser ejecutada. De hecho, all empieza y no debera salir de ella. Variables y Tipos de Datos En ensamblador todas nuestras variables de programa eran registros de la RAM crudos, es decir, datos de 8 bits sin formato. En los lenguajes de alto nivel estos registros son tratados de acuerdo con formatos que les permiten representar nmeros de 8, 16 32 bits (a veces ms grandes), con signo o sin l, nmeros enteros o decimales. Esos son los tipos de datos bsicos. Las variables de los compiladores pueden incluso almacenar matrices de datos del mismo tipo (llamadas arrays) o de tipos diferentes (llamadas estructuras). Estos son los tipos de datos complejos. Los siguientes son los principales tipos de datos bsicos del lenguaje C: Tipo de dato char signed char unsigned char (signed) int unsigned int (signed) long unsigned long float Tamao 8 8 8 16 16 32 32 32 Rango 0 a 255 -128 a 127 -128 a 127 0 a 255 -32,768 a 32,767 0 a 65,536 -2,147,483,648 a 2,147,483,647 0 a 4,294,967,295 +/- 1.18E38 a +/- 3.40E+38

Por desgracia, excepto signed char y unsigned char, los otros tipos establecen variables de tamaos y/o rangos que suelen varar de un compilador C a otro. Otros compiladores tambin manejan los tipos short, double, bool (o boolean), bit, etc. Esas divergencias pueden afectar la portabilidad de los cdigos, adems de confundir a los programadores. Los valores de esta tabla son los utilizados por la mayora de los compiladores C. Los especificadores signed (con signo) mostrados entre parntesis son opcionales. Es decir, da lo mismo poner int que signed int, por ejemplo. Es una redundancia que se suele usar para reforzar su condicin o para que se vea ms ilustrativo.

CURSO_MICROS

P g i n a | 64

Declaracin de variables Esta parte es comparable, aunque lejanamente a cuando identificbamos nuestras variables del ensamblador con las directivas equ o cblock endc. No se puede usar una variable si antes no se ha declarado. La forma general ms simple de hacerlo es la siguiente: data_type myvar; donde data_type es un tipo de dato bsico o complejo, del compilador o definido por el usuario y myvar es un identificador cualquiera, siempre que no sea palabra reservada. Ejemplos. unsigned char d; // Variable para enteros de 8 bits sin signo char b; // Variable de 8 bits (para almacenar // caracteres ascii) signed char c; // Variable para enteros de 8 bits con signo int i; // i es una variable int, con signo signed int j; // j tambin es una variable int con signo unsigned int k; // k es una variable int sin signo Tambin es posible declarar varias variables del mismo tipo, separndolas con comas. As nos ahorramos algo de tipeo. Por ejemplo: float area, side; // Declarar variables area y side de tipo float unsigned char a, b, c; // Declarar variables a, b y c como unsigned char Especificadores de tipo de datos A la declaracin de una variable se le puede aadir un especificador de tipo como const, static, volatile, extern, register, etc. Dichos especificadores tienen diversas funciones y, salvo const, se suelen usar en programas ms elaborados. Como no queremos enredarnos tan pronto, lo dejaremos para otro momento. Una variable const debe ser inicializada en su declaracin. Despus de eso el compilador solo permitir su lectura mas no su escritura. Ejemplos: const int a = 100; // Declarar constante a int b; // Declarar variable b //... b = a; b = 150; a = 60; a = b;

// Vlido // Vlido // Error! a es constante // Error! a es constante

Por ms que las variables constantes sean de solo lectura, ocuparn posiciones en la RAM del C. Por eso muchas veces es preferible definir las constantes del programa con las clsicas directivas #define (como lo hacamos en el ensamblador).

CURSO_MICROS

P g i n a | 65

#define a 100

// Definir constante a

Sentencias selectivas Llamadas tambin sentencias de bifurcacin, sirven para redirigir el flujo de un programa segn la evaluacin de alguna condicin lgica. Las sentencias if e ifelse son casi estndar en todos los lenguajes de programacin. Adems de ellas estn las sentencias ifelse escalonadas y switchcase. La sentencia if La sentencia if (si condicional, en ingls) hace que un programa ejecute una sentencia o un grupo de ellas si una expresin es cierta. Esta lgica se describe en el siguiente esquema.

Diagrama de flujo de la sentencia if. La forma codificada sera as: sentenciaA; if ( expression ) // Si expression es verdadera, // ejecutar el siguiente bloque { // apertura de bloque sentenciaB; sentenciaC; // algunas otras sentencias } // cierre de bloque sentenciaX; Despus de ejecutar sentenciaA el programa evala expression. Si resulta ser verdadera, se ejecutan todas las sentencias de su bloque y luego se ejecutar la sentenciaX.

CURSO_MICROS

P g i n a | 66

En cambio, si expression es falsa, el programa se saltear el bloque de if y ejecutar sentenciaX. La sentencia if else La sentencia if brinda una rama que se ejecuta cuando una condicin lgica es verdadera. Cuando el programa requiera dos ramas, una que se ejecute si cierta expression es cierta y otra si es falsa, entonces se debe utilizar la sentecia if else. Tiene el siguiente esquema.

Diagrama de flujo de la sentencia if else. Expresando lo descrito en cdigo C, tenemos: (Se lee como indican los comentarios.) SentenciaA; if ( expression ) // Si expression es verdadera, ejecutar { // este bloque sentenciaB; sentenciaC; // ... } else // En caso contrario, ejecutar este bloque { sentenciaM; sentenciaN; // ... } sentenciaX; // ... Como ves, es bastante fcil, dependiendo del resultado se ejecutar uno de los dos bloques de la sentencia if else, pero nunca los dos a la vez.

CURSO_MICROS

P g i n a | 67

La sentencia if else if escalonada Es la versin ampliada de la sentencia if else. En el siguiente boceto se comprueban tres condiciones lgicas, aunque podra haber ms. Del mismo modo, se han puesto dos sentencias por bloque solo para simplificar el esquema. if ( expression_1 ) // Si expression_1 es verdadera ejecutar { // este bloque sentencia1; sentencia2; } else if ( expression_2 ) // En caso contrario y si expression_2 es { // verdadera, ejecutar este bloque sentencia3; sentencia4; } else if ( expression_3 ) // En caso contrario y si expression_3 es { // verdadera, ejecutar este bloque sentencia5; sentencia6; } else // En caso contrario, ejecutar este bloque { sentencia7; sentencia8; }; // ; opcional // todo... Las expresiones se evualan de arriba abajo. Cuando alguna de ellas sea verdadera, se ejecutar su bloque correspondiente y los dems bloques sern salteados. El bloque final (de else) se ejecuta si ninguna de las expresiones es verdadera. Adems, si dicho bloque est vaco, puede ser omitido junto con su else. La sentencia switch La sentencia switch brinda una forma ms elegante de bifurcacin mltiple. Podemos considerarla como una forma ms estructurada de la sentencia if else if escalonada, aunque tiene algunas restricciones en las condiciones lgicas a evaluar, las cuales son comparaciones de valores enteros. Para elaborar el codigo en C se usan las palabras reservadas switch, case, break y default. El siguiente esquema presenta tres cases pero podra haber ms, as como cada bloque tambin podra tener ms sentencias. switch ( expression ) { case constante1: // Si expression = constante1, ejecutar este bloque sentencia1; sentencia2;

CURSO_MICROS

P g i n a | 68

break; case constante2: sentencia3; sentencia4; break; case constante3: sentencia5; sentencia6; break; default: // sentencia7; sentencia8; break; } sentenciaX; // todo...

// Si expression = constante2, ejecutar este bloque

// Si expression = constante3, ejecutar este bloque

// Si expression no fue igual a ninguna de las constantes anteriores, ejecutar este bloque

donde constante1, constante2 y constante3 deben ser constantes enteras, por ejemplo, 2, 0x45, a, etc. (a tiene cdigo ascii 165, que es, a fin de cuentas, un entero.) expresion puede ser una variable compatible con entero. No es una expresin que conduce a una condicin lgica como en los casos anteriores. El programa solo ejecutar uno de los bloques dependiendo de qu constante coincida con expression. Usualmente los bloques van limitados por llaves, pero en este caso son opcionales, dado que se pueden distinguir fcilmente. Los bloques incluyen la sentencia break. Qu es eso? La sentencia break hace que el programa salga del bloque de switch y ejecute la sentencia que sigue (en el boceto, sentenciaX). Atento!: de no poner break, tambin se ejecutar el bloque del siguiente case, sin importar si su constante coincida con expression o no. No sera necesario poner el default si su bloque estuviera vaco. Sentencias iterativas Las sentencias de control iterativas sirven para que el programa ejecute una sentencia o un grupo de ellas un nmero determinado o indeterminado de veces. As es, esta seccin no habla de otra cosa que de los bucles en C. El lenguaje C soporta tres tipos de bucles, las cuales se construyen con las sentencias while, do while y for. El segundo es una variante del primero y el tercero es una versin mas compacta e intuitiva del bucle while. La sentencia while El cuerpo o bloque de este bucle se ejecutar una y otra vez mientras (while, en ingls) una expresin sea verdadera.

CURSO_MICROS

P g i n a | 69

50Diagrama de flujo de las sentencia while. El bucle while en C tiene la siguiente sixtaxis y se lee as: mientras (while) expression sea verdadera, ejecutar el siguiente bloque. sentenciaA; while ( expression ) // Mientras expression sea verdadera, ejecutar el // siguiente bloque { sentenciaB; sentenciaC; // ... }; // Este ; es opcional sentenciaX; // ... Nota que en este caso primero se evala expression. Por lo tanto, si desde el principio expression es falsa, el bloque de while no se ejecutar nunca. Por otro lado, si expression no deja de ser verdadera, el programa se quedar dando vueltas para siempre. La sentencia do - while Como dije antes, es una variacin de la sentencia while simple. La principal diferencia es que la condicin lgica (expression) de este bucle se presenta al final. Como se ve en la siguiente figura, esto implica que el cuerpo o bloque de este bucle se ejecutar al menos una vez.

CURSO_MICROS

P g i n a | 70

Diagrama de flujo de las sentencia do while. La sintaxis para la sentencia do while es la siguiente y se lee: Ejecutar (do) el siguiente bloque, mientras (while) expression sea verdadera. sentenciaA; do { sentenciaB; sentenciaC; // ... } while ( expression ); // Este ; es mandatorio sentenciaX; // ... La sentencia for Las dos sentencias anteriores, while y do while, se suelen emplear cuando no se sabe de antemano la cantidad de veces que se va a ejecutar el bucle. En los casos donde el bucle involucra alguna forma de conteo finito es preferible emplear la sentencia for. (Inversamente, al ver un for en un programa, debemos suponer que estamos frente a algn bucle de ese tipo.) sta es la sintaxis general de la sentencia for en C: for ( expression_1 ; expression_2 ; expression_3 ) { sentencia1; sentencia2; // ... }; // Este ; es opcional Ahora veamos por partes cmo funciona: expression_1 suele ser una sentencia de inicializacin.

CURSO_MICROS

P g i n a | 71

expression_2 se evuala como condicin lgica para que se ejecute el bloque. expression_3 es una sentencia que debera poner coto a expression_2.

Por la forma y orden en que se ejecutan estas expresiones, el bucle for es equivalente a la siguiente construccin, utilizando la sentencia while. Primero se ejecuta expression_1 y luego se ejecuta el bloque indicado tantas veces mientras expression_2 sea verdadera. expression_1; while ( expression_2 ) { sentencia1; sentencia2; // ... expression_3; } No obstante, de esa forma se ve ms rara an; as que, mejor, veamos estos ejemplos, que son sus presentaciones ms clsicas. (i es una variable y a y b son constantes o variables): for ( i = 0 ; i < 10 ; i++ ) { sentencias; } Se lee: para (for) i igual a 0 hasta que sea menor que 10 ejecutar sentencias. La sentencia i++ indica que i se incrementa tras cada ciclo. As, el bloque de for se ejecutar 10 veces, desde que i valga 0 hasta que valga 9. En este otro ejemplo las sentencias se ejecutan desde que i valga 10 hasta que valga 20. Es decir, el bucle dar 11 vueltas en total. for ( i = 10 ; i <= 20 ; i++ ) { sentencias; } El siguiente bucle for empieza con i inicializado a 100 y su bloque se ejecutar mientras i sea mayor o igual a 0. Por supuesto, en este caso i se decrementa tras cada ciclo. for ( i = 100 ; i >= 0 ; i-- ) { sentencias; } Se pueden hacer muchas ms construcciones, todas coincindentes con la primera plantilla, pero tambin son menos frecuentes.

CURSO_MICROS

P g i n a | 72

Sentencias con bloques simples Cuando las sentencias selectivas (como if) o de bucles (como while o for) tienen cuerpos o bloques que constan de solo una sentencia, se pueden omitir las llaves. Aun as, es aconsejable seguir manteniendo las tabulaciones para evitarnos confusiones. Por ejemplo, las siguientes sentencias: if(a > b) { a = 0; } if(a == b) { a++; } else { b--; } while( a >= b) { a = a + b; } for(i=0; i<=10; i++) { a = a*2; } bien se pueden escribir de la siguiente forma: if(a > b) a = 0; if(a == b) a++; else b--; while( a >= b) a = a + b; for(i=0; i<=10; i++) a = a*2; Los operadores Sirven para realizar operaciones aritmticas, lgicas, comparativas, etc. Segn esa funcin se clasifican en los siguientes grupos.

CURSO_MICROS

P g i n a | 73

Operadores aritmticos Adems de los tpicos operadores de suma, resta, multiplicacion y divisin, estn los operadores de mdulo, incremento y decremento. Operador Suma + * / % ++ -Resta Multiplicacin Divisin Mdulo. Retorna el residuo de una divisin entera. Solo se debe usar con nmeros enteros. Incrementar en uno Decrementar en uno Accin

Ejemplos: int a, b, c; // Declarar variables a, b y c // Sumar a y b. Almacenar resultado en c // Multiplicar b por c. Resultado en b // Dividir a entre c. Colocar resultado en b // Sumar a y c y restarle b. Resultado en a // Dividir a+b entre c. Resultado en c // Sumar a ms b/c ms bb. Resultado en b // Residuo de dividir ab a c // Incrementar a en 1 // Decrementar b en 1 // Incrementar c en 1 // Decrementar b en 1

c = a + b; b = b * c; b = a / c; a = a + c b; c = (a + b) / c; b = a + b / c + b * b; c = a % b; a++; b--; ++c; --b;

Te recordaron a tus clases de lgebra del colegio? A diferencia de esas matemticas, estas expresiones no son ecuaciones; significan las operaciones que indican sus comentarios. Por lo visto, los operadores ++ --funcionan igual si estn antes o despus de una variable en una expresin simple. Sin embargo, hay una forma (tal vez innecesaria y confusa para un novato, pero muy atractiva para los que ya estamos acostumbrados a su uso) que permite escribir cdigo ms compacto, es decir, escribir dos sentencias en una. Si ++ oestn antes del operando, primero se suma o resta 1 al operando y luego se evala la expresin. Si ++ oestn despus del operando, primero se evala la expresin y luego se suma o resta 1 al operando.

CURSO_MICROS

P g i n a | 74

int a, b; a = b++; a = ++b; if (a++ < 10) { // algn cdigo } if (++a < 10) { // algn cdigo } Operadores de bits

// Declarar variables enteras a y b // Lo mismo que a = b; y luego b = b + 1; // Lo mismo que b = b + 1; y luego a = b; // Primero comprueba si a < 10 y luego // incrementa a en 1

// Primero incrementa a en 1 y luego // comprueba si a < 10

Se aplican a operaciones lgicas con variables a nivel binario. Aqu tenemos las clsicas operaciones AND, OR inclusiva, OR exclusiva y la NEGACIN. Adicionalmente, he incluido en esta categora los operaciones de desplazamiento a la derecha y la izquierda. Si bien son operaciones que producen resultados anlogos a los de las instrucciones de ensamblador iorlw y iorwf para la OR inclusiva, xorlw y xorwf para la OR exclusiva, andlw y andwf para la AND y comf para la negacin; los operadores lgicos del C pueden operar sobre variables de distintos tamaos, ya sean de 1, 8, 16 32 bits. Operador Accin & AND a nivel de bits | ^ ~ << >> Ejemplos: char m; int n; // variable de 8 bits // variable de 16 bits // m ser 0x48 // Despus de esto m ser 0x08 // Despus de esto m ser 0x2F // Despus de esto m ser 0x20 // n ser 0xFF00 // n ser 0x00FF // Setear bits 0 y 7 de variable m // Limpiar nibble bajo de variable m OR inclusiva a nivel de bits OR exclusiva a nivel de bits Complemento a uno a nivel de bits Desplazamiento a la izquierda Desplazamiento a la derecha

m = 0x48; m = m & 0x0F; m = m | 0x24; m = m & 0b11110000; n = 0xFF00; n = ~n; m = m | 0b10000001; m = m & 0xF0;

CURSO_MICROS

P g i n a | 75

m = m ^ 0b00110000; // Invertir bits 4 y 5 de variable m m = 0b00011000; // Cargar m con 0b00011000 m = m >> 2; // Desplazar m 2 posiciones a la derecha // Ahora m ser 0b00000110 n = 0xFF1F; n = n << 12; // Desplazar n 12 posiciones a la izquierda // Ahora n ser 0xF000; m = m << 8; // Despus de esto m ser 0x00 Fjate en la semejanza entre las operaciones de desplazamiento con >> y << y las operaciones del rotacin del ensamblador. La diferencia es que cuando una variable se desplaza hacia un lado, los bits que salen por all se pierden y los bits que entran por el otro lado son siempre ceros. Es por esto que en la ltima sentencia, m = m << 8, el resultado es 0x00. Por cierto, en el lenguaje C no existen operadores de rotacin. Hay formas alternativas de realizarlas.

Desplazamientos producidos por los operadores << y >>. Operadores relacionales Se emplean para construir las condiciones lgicas de las sentencias de control selectivas e iterativas, como ya hemos podido apreciar en las secciones anteriores. La siguiente tabla muestra los operadores relacionales disponibles. Operador Accin == Igual != > < >= <= No igual Mayor que Menor que Mayor o igual que Menor o igual que

Operadores lgicos Generalmente se utilizan para enlazar dos o ms condiciones lgicas simples. Por suerte, estos operadores solo son tres y sern explicados en las prcticas del curso.

CURSO_MICROS

P g i n a | 76

Operador Accin && AND lgica || ! OR lgica Negacin lgica

Ejemplos: if( !(a==0) ) { // sentencias } // Si a igual 0 sea falso

if( (a<b) && (a>c) ) { // sentencias }

// Si a<b y a>c son verdaderas

while( (a==0) || (b==0) ) // Mientras a sea 0 b sea 0 { // sentencias } Composicin de operadores Se utiliza en las operaciones de asignacin y nos permite escribir cdigo ms abreviado. La forma general de escribir una sentencia de asignacin mediante los operadores compuestos es: obtect op= expression; que es equivalente a la sentencia object = object op expression; op puede ser cualquiera de los operadores aritmticos o de bit estudiados arriba. O sea, op puede ser +, - , *, /, %, &, | , ^, ~,<< >>. Nota: no debe haber ningn espacio entre el operador y el signo igual. Ejemplos: int a; a += 50; a += 20; a *= 2; a &= 0xF0; a <<= 1; // Declarar a // Es lo mismo que a = a + 50; // Tambin significa sumarle 20 a a // Es lo mismo que a = a * 2; // Es lo mismo que a = a & 0xF0; // Es lo mismo que a = a << 1;

CURSO_MICROS

P g i n a | 77

Precedencia de operadores Una expresin puede contener varios operadores, de esta forma: b = a * b + c / b; // a, b y c son variables

A diferencia del lenguaje Basic, donde la expresin se evala de izquierda a derecha, en esta sentencia no queda claro en qu orden se ejecutarn las operaciones indicadas. Hay ciertas reglas que establecen dichas prioridades; por ejemplo, las multiplicaciones y divisiones siempre se ejecutan antes que las sumas y restas. Pero es ms prctico emplear los parntesis, los cuales ordenan que primero se ejecuten las operaciones de los parntesis ms internos. Eso es como en el lgebra elemental de la escuela, as que no profundizar. Por ejemplo, las tres siguientes sentencias son diferentes. b = (a * b) + (c / b); b = a * (b + (c / b)); b = ((a * b) + c)/ b); Tambin se pueden construir expresiones condicionales, as: if ( (a > b) && ( b < c) ) // Si a>b y b<c, ... { // ... }

Las funciones Una funcin es un bloque de sentencias identificado por un nombre y puede recibir y devolver datos. En bajo nivel, en general, las funciones operan como las subrutinas de assembler, es decir, al ser llamadas, se guarda en la Pila el valor actual del PC (Program Counter), despus se ejecuta todo el cdigo de la funcin y finalmente se recobra el PC para regresar de la funcin. Dada su relativa complejidad, no es tan simple armar una plantilla general que represente a todas las funciones. El siguiente esquema es una buena aproximacin. data_type1 function_name (data_type2 arg1, data_type3 arg2, ... ) { // Cuerpo de la funcin // ... return SomeData; // Necesario solo si la funcin retorna algn valor } Donde:

CURSO_MICROS

P g i n a | 78

function_name es el nombre de la funcin. Puede ser un identificador cualquiera. data_type1 es un tipo de dato que identifica el parmetro de salida. Si no lo hubiera, se debe poner la palabra reservada void (vaco, en ingls). arg1 y arg2 (y puede haber ms) son las variables de tipos data_type1, data_type2..., respectivamente, que recibirn los datos que se le pasen a la funcin. Si no hay ningn parmetro de entrada, se pueden dejar los parntesis vacos o escribir un void entre ellos.

Funciones sin parmetros Para una funcin que no recibe ni devuelve ningn valor, la plantilla de arriba se reduce al siguiente esquema: void function_name ( void ) { // Cuerpo de la funcin } Y se llama escribiendo su nombre seguido de parntesis vacos, as: function_name(); La funcin principal main es otro ejemplo de funcin sin parmetros. Dondequiera que se ubique, siempre debera ser la primera en ejecutarse; de hecho, no debera terminar. void main (void) { // Cuerpo de la funcin } Funciones con parmetros (por valor) De momento, solo estudiaremos las funciones que pueden tener varios parmetros de entrada pero solo uno de salida. Si la funcin no tiene parmetros de entrada o de salida, debe escribirse un void en su lugar. El valor devuelto por una funcin se indica con la palabra reservada return. Segn el comportamiento de los parmetros de entrada de la funcin, estos se dividen en parmetros por valor y parmetros por referencia. Lo expuesto en este apartado corresponde al primer grupo porque es el caso ms ampliamente usado. Con esto en mente podemos seguir. Para llamar a una funcin con parmetros es importante respetar el orden y el tipo de los parmetros que ella recibe. El primer valor pasado corresponde al primer parmetro de entrada; el segundo valor, al segundo parmetro; y as sucesivamente si hubiera ms. Cuando una variable es entregada a una funcin, en realidad se le entrega una copia suya. De este modo, el valor de la variable original no ser alterado. Mejor, plasmemos todo esto en el siguiente ejemplo.

CURSO_MICROS

P g i n a | 79

int minor ( int arg1, int arg2, int arg3 ) { int min; // Declarar variable min min = arg1; // Asumir que el menor es arg1 if ( arg2 < min ) // Si arg2 es menor que min min = arg2; // Cambiar a arg2 if ( arg3 < min ) // Si arg3 es menor que min min = arg3; // Cambiar a arg3 return min; // Retornar valor de min } void main (void) { int a, b, c, d; // Declarar variables a, b, c y d /* Aqu asignamos algunos valores iniciales a a, b y c */ /* ... */ d = minor(a,b,c); // Llamar a minor // En este punto d debera ser el menor entre a, b y c while (1); // Bucle infinito } En el programa mostrado la funcin minor recibe tres parmetros de tipo int y devuelve uno, tambin de tipo int, que ser el menor de los nmeros recibidos. El mecanismo funciona as: siempre respetando el orden, al llamar a minor el valor de a se copiar a la variable arg1; el valor de b, a arg2 y el valor de c, a arg3. Despus de ejecutarse el cdigo de la funcin, el valor de retorno (min en este caso) ser copiado a una variable temporal y de all pasar a d. Aunque el C no es tan implacable con la comprobacin de tipos de datos como Pascal, siempre deberamos revisar que los datos pasados sean compatibles con los que la funcin espera, as como los datos recibidos, con los que la funcin devuelve. Por ejemplo, estara mal llamar a la funcin minor del siguiente modo: d = minor(-15, 100, 5.124); // Llamar a minor Aqu los dos primeros parmetros estn bien, pero el tercero es un nmero decimal (de 24 32 bits), no compatible con el tercer parmetro que la funcin espera (entero de 8 16 bits). En estos casos el compilador nos mostrar mensajes de error, o cuando menos de advertencia. Parmetros por referencia La funcin que recibe un parmetro por referencia puede cambiar el valor de la variable pasada. La forma clsica de estos parmetros se puede identificar por el uso del smbolo &, tal como se ve en el siguiente boceto de funcin. int minor ( int & arg1, int & arg2, int & arg3 ) { // Cuerpo de la funcin. // arg1, arg2 y arg3 son parmetros por referencia.

CURSO_MICROS

P g i n a | 80

// Cualquier cambio hecho a ellos desde aqu afectar a las variables // que fueron entregadas a esta funcin al ser llamada. } No voy profundizar al respecto porque he visto que muchos compiladores C no soportan esta forma. Otra forma de pasar un parmetro por referencia es mediante los punteros, pero eso lo dejamos para el final porque no es nada nada fcil para un novato. Prototipos de funciones El prototipo de una funcin le informa al compilador las caractersticas que tiene, como su tipo de retorno, el nmero de parmetros que espera recibir, el tipo y orden de dichos parmetros. Por eso se deben declarar al inicio del programa. El prototipo de una funcin es muy parecido a su encabezado, se pueden diferenciar tan solo por terminar en un punto y coma (;). Los nombres de las variables de entrada son opcionales. Por ejemplo, en el siguiente boceto de programa los prototipos de las funciones main, func1 y func2 declaradas al inicio del archivo permitirn que dichas funciones sean accedidas desde cualquier parte del programa. Adems, sin importar dnde se ubique la funcin main, ella siempre ser la primera en ejecutarse. Por eso su prototipo de funcin es opcional. #include <pic.h> void func1(char m, long p); // Prototipo de funcin func1 char func2(int a); // Prototipo de funcin func2 void main(void); // Prototipo de funcin main. Es opcional void main(void) { // Cuerpo de la funcin // Desde aqu se puede acceder a func1 y func2 } void func1(char m, long p) { // Cuerpo de la funcin // Desde aqu se puede acceder a func2 y main } char func2(int a) { // Cuerpo de la funcin // Desde aqu se puede acceder a func1 y main } La llamada a main, por supuesto, no tiene sentido; solo lo pongo para ilustrar. Si las funciones no tienen prototipos, el acceso a ellas ser restringido. El compilador solo ver las funciones que estn implementadas encima de la funcin llamadora o, de lo contrario, mostrar errores de funcin no definida. El siguiente boceto ilustra este hecho. (Atiende a los comentarios.)

CURSO_MICROS

P g i n a | 81

#include <pic.h> void main(void) { // Cuerpo de la funcin // Desde aqu no se puede acceder a func1 ni func2 porque estn abajo } void func1(char m, long p) { // Cuerpo de la funcin // Desde aqu se puede acceder a main pero no a func2 } char func2(int a) { // Cuerpo de la funcin // Desde aqu se puede acceder a func1 y main } Para terminar, dado que los nombres de las variables en los parmetros de entrada son opcionales, los prototipos de func1 y func2 tambin se pueden escribir asi void func1(char, long); char func2(int );

Variables locales y variables globales Los lenguajes de alto nivel como el C fueron diseados para desarrollar los programas ms grandes y complejos que se puedan imaginar, programas donde puede haber cientos de variables, entre otras cosas. Imaginas lo que significara buscar nombres para cada variable si todos tuvieran que ser diferentes? Pues bien, para simplificar las cosas, el C permite tener varias variables con el mismo nombre. As es. Esto es posible gracias a que cada variable tiene un mbito, un rea desde donde ser accesible. Hay diversos tipos de mbito, pero empezaremos por familiarizarnos con los dos ms usados, que corresponden a las variables globales y variables locales. Las variables declaradas fuera de todas las funciones y antes de sus implementaciones tienen carcter global y podrn ser accedidas desde todas las funciones. Las variables declaradas dentro de una funcin, incluyendo las variables del encabezado, tienen mbito local. Ellas solo podrn ser accedidas desde el cuerpo de dicha funcin. De este modo, puede haber dos o ms variables con el mismo nombre, siempre y cuando estn en diferentes funciones. Cada variable pertenece a su funcin y no tiene nada que ver con las variables de otra funcin, por ms que tengan el mismo nombre. En la mayora de los compiladores C para PICs las variables locales deben declararse al principio de la funcin.

CURSO_MICROS

P g i n a | 82

Por ejemplo, en el siguiente boceto de programa hay dos variables globales (speed y limit) y cuatro variables locales, tres de las cuales se llaman count. Atiende a los comentarios. char foo(long ); // Prototipo de funcin

int speed; // Variable global const long limit = 100; // Variable global constante void inter(void) { int count; // Variable local /* Este count no tiene nada que ver con el count de las funciones main o foo */ speed++; // Acceso a variable global speed vari = 0; // Esto dar ERROR porque vari solo pertenece // a la funcin foo. No compilar. } void main(void) { int count; // Variable local count /* Este count no tiene nada que ver con el count de las funciones inter o foo */ count = 0; // Acceso a count local speed = 0; // Acceso a variable global speed } char foo(long count) // Variable local count { int vari; // Variable local vari } Algo muy importante: a diferencia de las variables globales, las variables locales tienen almacenamiento temporal, es decir, se crean al ejecutarse la funcin y se destruyen al salir de ella. Qu significa eso? Lo explico en el siguiente apartado. Si dentro de una funcin hay una variable local con el mismo nombre que una variable global, la precedencia en dicha funcin la tiene la variable local. Si te confunde, no uses variables globales y locales con el mismo nombre. Variables static Antes de nada debemos aclarar que una variable static local tiene diferente significado que una variable static global. Ahora vamos a enfocarnos al primer caso por ser el ms comn. Cuando se llama a una funcin sus variables locales se crearn en ese momento y cuando se salga de la funcin se destruirn. Se entiende por destruir al hecho de que la locacin de memoria que tena una variable ser luego utilizada por el compilador para otra variable local (as se economiza la memoria). Como consecuencia, el valor de las variables locales no ser el mismo entre llamadas de funcin. Por ejemplo, revisa la siguiente funcin, donde a es una variable local ordinaria.

CURSO_MICROS

P g i n a | 83

void increm() { int a; // Declarar variable a a++; // Incrementar a } Cualquiera que haya sido su valor inicial, crees que despus de llamar a esta funcin 10 veces, el valor de a se habr incrementado en 10?... Pues, no necesariamente. Cada vez que se llame a increm se crea a, luego se incrementa y, al terminar de ejecutarse la funcin, se destruye. Para que una variable tenga una locacin de memoria independiente y su valor no cambie entre llamadas de funcin tenemos dos caminos: o la declaramos como global, o la declaramos como local esttica. Los buenos programadores siempre eligen el segundo. Una variable se hace esttica anteponiendo a su declaracin el especificador static. Por defecto las variables estticas se autoinicializan a 0, pero se le puede dar otro valor en la misma declaracin (dicha inicializacin solo se ejecuta la primera vez que se llama a la funcin), as: static int var1; // Variable static (inicializada a 0 por defecto) static int var2 = 50; // Variable static inicializada a 50 Ejemplos. void increm() { static int a = 5; // Variable local esttica inicializada a 5 a++; // Incrementar a } void main() { int i; // Declarar variable i // El siguiente cdigo llama 10 veces a increm for(i=0; i<10; i++) increm(); // Ahora la variable a s debera valer 15 while(1); // Bucle infinito } Variables volatile A diferencia de los ensambladores, los compiladores tienen cierta inteligencia. Es decir, piensan un poco antes de traducir el cdigo fuente en cdigo ejecutable. Por ejemplo, veamos el siguiente pedazo de cdigo para saber lo que suele pasar con una variable ordinaria: int var; //... var = var; // Declarar variable var // Asignar var a var

El compilador creer (probablemente como nosotros) que la sentencia var = var no tiene sentido (y quiz tenga razn) y no la tendr en cuenta, la ignorar. sta es solo una

CURSO_MICROS

P g i n a | 84

muestra de lo que significa optimizacin del cdigo. Luego descubrirs ms formas de ese trabajo. El ejemplo anterior fue algo burdo, pero habr cdigos con redundancias aparentes y ms difciles de localizar, cuya optimizacin puede ser contraproducente. El caso ms notable que destacan los manuales de los compiladores C para microcontroladores es el de las variables globales que son accedidas por la funcin de interrupcin y por cualquier otra funcin. Para que un compilador no intente pasarse de listo con una variable debemos declararla como volatile, anteponindole dicho calificador a su declaracin habitual. Por ejemplo, en el siguiente boceto de programa la variable count debe ser accedida desde la funcin interrupt como desde la funcin main; por eso se le declara como volatile. Nota: el esquema de las funciones de interrupcin suele variar de un compilador a otro. ste es solo un ejemplo. volatile int count; // count es variable global voltil

void interrupt(void) // Funcin de interrupcin { // Cdigo que accede a count } void main(void) // Funcin principal { // Cdigo que accede a count }

Arrays y Punteros Probablemente ste sea el tema que a todos nos ha dado ms de un dolor de cabeza y que ms hemos reledo para captarlo a cabalidad. Hablo ms bien de los punteros. Si ellos el C no sera nada, perdera la potencia por la que las mejores empresas lo eligen para crear sus softwares de ordenadores. Pero bueno, regresando a lo nuestro, estos temas se pueden complicar muchsimo ms de lo que veremos aqu. Solo veremos los arrays unidimensionales y los punteros (que en principio pueden apuntar a todo tipo de cosas) los abocaremos a los datos bsicos, incluyendo los mismos arrays. Aun as, te sugiero que tengas un par de aspirinas al lado. Los arrays o matrices Un array es una mega variable compuesto de un conjunto de variables simples del mismo tipo y ubicadas en posiciones contiguas de la memoria. Con los arrays podemos hacer todos lo que hacamos con las tablas (de bsqueda) del ensamblador y muchsimo ms. Un array completo tiene un nombre y para acceder a cada uno de sus elementos se utilizan ndices entre corchetes ([ ]). Los ndices pueden estar indicados por variables o constantes. En el siguiente esquema se ve que el primer elemento de un array tiene ndice 0 y el ltimo, N-1, siendo N la cantidad de elementos del array.

CURSO_MICROS

P g i n a | 85

Estructura de un array unidimensional de N elementos. Declaracin de arrays Para declarar un array unidimensional se utiliza la siguiente sintaxis: data_type identifier[ NumElementos ]; Donde data_type es un tipo de dato cualquiera, identifier es el nombre del array y NumElementos es la cantidad de elementos que tendr (debe ser un valor constante). De este modo, el ndice del primer elemento es 0 y el del ltimo es NumElements - 1. Por ejemplo, las siguientes lneas declaran tres arrays. char letters10]; // letters es un array de 10 elementos de tipo char long HexTable[16]; // HexTable es un array de 16 elementos de tipo long int address[100]; // address es un array de 100 elementos de tipo int Para el array letters el primer elemento es letters[0] y el ltimo, letters[9]. As, tenemos 10 elementos en total. Si quisiramos asignar a cada uno de los elementos de letters los caracteres desde la a hasta la j, lo podramos hacer individualmente as: letters[0] = a; letters[1] = b; letters[2] = c; letters[3] = d; letters[4] = e; letters[5] = f; letters[6] = g; letters[7] = h; letters[8] = i; letters[9] = j; // Aqu el ndice es 0 // Aqu el ndice es 1 // ... //

// Aqu el ndice es 9

Pero as no tiene gracia utilizar arrays, verdad? En este caso lo mejor es utilizar un bucle, as: (Nota: los caracteres son, al fin y al cabo, nmeros en cdigos ascii y se les puede comparar.) char c; for ( c = a; c <= j; c++ ) letters[i] = c; Inicializacin de arrays Los elementos de un array se pueden inicializar junto con su declaracin. Para ello se le asigna una lista ordenada de valores encerrados por llaves y separados por comas. Por supuesto, los valores deben ser compatibles con el tipo de dato del array. Este tipo de inicializacin solo est permitido en la declaracin del array.

CURSO_MICROS

P g i n a | 86

Ejemplos: unsigned char mask[3] = { 0xF0, 0x0F, 0x3C }; // Ok int a[5] = { 20, 56, 87, -58, 5000 }; // Ok char vocals[5] = { a, e, i, o, u }; // Ok int c[4] = { 5, 6, 0, -5, 0, 4 }; // Error, demasiados inicializadores Tambin es posible inicializar un array sin especificar en su declaracin el tamao que tendr, dejando los corchetes vacos. El tamao ser precalculado y puesto por el compilador. sta es una forma bastante usada en los arrays de texto, donde puede resultar muy incmodo estar contando las letras de una cadena. Por ejemplo: int a[] = { 70, 1, 51 }; // Un array de 3 elementos char vocals[] = { a, e, i, o, u }; // Un array de 5 elementos char msg[] = Este es un array de caracteres; // Un array of 31 elementos Por qu el ltimo array tiene 31 elementos si solo se ven 30 letras? Lo sabremos luego. Cadenas de texto terminadas en nulo Son arrays de tipo de dato char. Hay dos caractersticas que distinguen a estas cadenas de los dems arrays. Primero: su inicializacin se hace empleando comillas dobles y segundo, el ltimo trmino del array es un carcter NULL (simplemente un 0x00). De ah su nombre. Ejemplos: char Greet[10] = Hello; char msg[] = Hello; // Un array de 10 elementos // Un array de 6 elementos

El array Greet tiene espacio para 10 elementos, de los cuales solo los 5 primeros han sido llenados con las letras de Hello, el resto se rellena con ceros. El array msg tiene 6 elementos porque adems de las 5 letras de Hello se le ha aadido un Null (0x00) al final (claro que no se nota). Es decir, la inicializacin de msg es equivalente a: char msg[] = { H, e, l, l, o, 0x00}; // Un array de 6 elementos Visto grficamente, msg tendra la siguiente representacin:

Estructura de una cadena de texto. Los punteros Los punteros suelen ser el tema que ms cuesta entender en programacin. Pero si ya llegaste aqu, es el momento menos indicado para detenerte.

CURSO_MICROS

P g i n a | 87

Los punteros son un tipo de variables muy especial. Son variables que almacenan las direcciones fsicas de otras variables. Si tenemos la direccin de una variable, tenemos acceso a esa variable de manera indirecta y podemos hacer con ellas todo lo que queramos ;). Declaracin de punteros Los punteros pueden apuntar a todo tipo de variables, pero no a todas al mismo tiempo. La declaracin de un puntero es un tanto peculiar. En realidad, se parece a la declaracin de una variable ordinaria solo que se pone un asterisco de por medio. En este punto debes recordar las declaraciones de todo tipo de variables que hemos visto, incluyendo las influenciadas por los calificadores const, static, etc. Todas excepto los arrays; por qu? La forma general de declarar un puntero es la siguiente: data_type * PointerName; Los siguientes ejemplos muestran lo fcil que es familiarizarse con la declaracin de los punteros: int * ip; // ip es un puntero a variable de tipo int char * ucp; // cp es un puntero a variable de tipo char unsigned char * ucp; // Puntero a variable de tipo unsigned char const long * clp; // Puntero a constante de tipo long float * p1, *p2; // Declara dos punteros a variable de tipo float Apuntando a variables Decimos que una variable puntero apunta a una variable x si contiene la direccin de dicha variable. Para ello se utiliza el operador &, el cual extrae la direccin de la variable a la que acompaa. Un puntero siempre debera apuntar a una variable cuyo tipo coincida con el tipo del puntero. En los siguientes ejemplos vemos cmo apuntar a variables de tipo bsico, como int, char o float. Ms adelante veremos cmo apuntar a arrays. void main (void) { int height, width; char a, b, c; float max; int * ip; char * cp; float * fp; ip = &height; ip = &width; cp = &a; cp = &c; cp = &a; // ip es un puntero a variable tipo int // cp es un puntero a variable tipo char // Puntero a variable tipo float // Con esto ip tendr la direccin de height // Ahora ip apunta a width // cp apunta a a // Ahora cp apunta a c // Ahora cp apunta a a otra vez

CURSO_MICROS

P g i n a | 88

fp = &max; fp = &height; //... }

// fp apunta a max // Error! height no es una variable float

Asignaciones indirectas mediante punteros Una vez que un puntero apunte a una variable cualquiera, se puede acceder a dicha variable utilizando el nombre del puntero precedido por un asterisco, de esta forma: void main (void) { int height, width, n; // Variables ordinarias int * p, * q; // p y q son punteros a variables de tipo int p = &height; *p = 10; p = &width; *p = 50; height = *p; q = &height; n = (*p + *q)/2; //... } La expresin *p se debera leer: la variable apuntada por p. Eso tambin ayuda mucho a comprender a los punteros. Y para esto se inventaron los punteros? Yo me preguntaba lo mismo en mis inicios. El tema de los punteros se puede complicar casi hasta el infinito, por eso quiero ir con cuidado y poco a poco para que nadie se pierda. Punteros y arrays Cmo se declara un puntero a un array? Un puntero a un array es simplemente un puntero al tipo de dato del array. Cuando se asigna un puntero a un array, en realidad el puntero toma la direccin de su primer elemento, a menos que se especifique otro elemento. Luego, bastara con modificar el valor del puntero para que apunte a los otros elementos del array. Todo lo indicado se refleja en el siguiente cdigo: void main (void) { int * p; // Declara p como puntero a int int n; // Alguna variable int mat[3] = { 78, 98, 26 }; // Array de variables int // p apunta a height // Esto es como height = 10 // p apunta a width // Esto es como width = 50 // Esto es como height = width // q apunta a height // Esto es como n = (height + width)/2

CURSO_MICROS

P g i n a | 89

p = &mat; n = *p; p++; n = *p; p++; n = *p; *p = 10; p--; *p = 100; p = mat; p = NULL; // ... }

// p apunta a mat (a su primer elemento) // Esto da n = 78 // Incrementar p para apuntar a siguiente elemento // Esto da n = 98 // Incrementar p para apuntar a siguiente elemento // Esto da n = 26 // Con esto mat[3] valdr 10 // Decrementar p para apuntar a elemento anterior // Con esto mat[2] valdr 100 // p apunta a mat. Es lo mismo que p = &mat // Desasignar p. Lo mismo que p = 0x0000

En el fondo los arrays y los punteros trabajan de la misma forma, por lo menos cuando referencian a variables almacenadas en la RAM del microcontrolador. La nica diferencia es que los arrays no pueden direccionar a datos diferentes de su contenido; por eso tambin se les llama punteros estticos. En la prctica esto significa que un array es siempre compatible con un puntero, pero un puntero no siempre es compatible con un array. Por ejemplo, a un array no se le puede asignar otro array ni se le pueden sumar o restar valores para que apunten a otros elementos. Por lo dems, las operaciones de asignacin son similares para punteros y arrays, tal como se puede apreciar en el siguiente cdigo. (Por si las moscas, str1 es el array y str2, el puntero.) void main(void) { char str1[] = { A, r, r, a, y }; char * str2 = { P, o, i, n, t, e, r }; char a; a = str1[0]; a = str1[3]; a = str2[0]; a = str2[3]; str1 += 2; str2 += 2; str1++; str2++; a = *str2; // ... } // Esto da a = A // Esto da a = a // Esto da a = P // Esto da a = n // Error! Str1 es esttico // Correcto. Ahora str2 apunta a i // Error otra vez! Str1 es esttico // Correcto. Ahora str2 apunta a n // Esto da a = n

CURSO_MICROS

P g i n a | 90

Paso de punteros y arrays a funciones Recuerdas el paso de variables por valor y por referencia? Pues aqu vamos de nuevo. Bien, recordemos: una variable pasada por valor a una funcin, en realidad le entrega una copia suya; por lo que la variable original no tiene por qu ser afectada por el cdigo de la funcin. Ahora bien, pasar una variable por referencia significa que se pasa la direccin de dicha variable. Como consecuencia, la funcin tendr acceso a la variable original y podr modificar su contenido. Esto podra resultar riesgoso, pero, bien usada, la tcnica es una potente arma. Ya que los punteros operan con direcciones de variables, son el medio ideal para trabajar con parmetros por referencia. Hay dos casos de particular inters: uno, cuando deseamos en serio que la variable pasada a la funcin cambie a su regreso; y dos, cuando la variable pasada es demasiado grande (un array) como para trabajar con copias. De hecho, los arrays siempre se pasan por referencia ya que tambin son punteros al fin. La sintaxis de los punteros en el encabezado de la funcin no es nada nuevo, teniendo en cuenta que tambin tienen la forma de declaraciones de variables. En el siguiente ejemplo la funcion interchage intercambia los valores de las dos variables recibidas. En seguida explicar por qu vara un poco la forma en que se llama a la funcin. void interchange( int * p1, int * p2 ) { int tmp = *p1; // Guardar valor inicial de variable apuntada por p1. *p1 = *p2; // Pasar valor de variable apuntada por p2 a... // variable apuntada por p1. *p2 = tmp; // Variable apuntada por p2 valdr tmp. } void main (void) { int i, j; /* Hacer algunas asignaciones */ i = 10; j = 15; /* Llamar a funcin interchange pasando las direcciones de i y j */ interchange( &i, &j ); // En este punto i vale 15 y j vale 10 // ... } Al llamar a interchange le entregamos &i y &j, es decir, las direcciones de i y j. Por otro lado, la funcin interchange recibir dichos valores en p1 y p2, respectivamente. De ese modo, p1 y p2 estararn apuntando a i y j, y podremos modificar sus valores. Ten presente que se mantiene la forma de asignacin puntero = &variable (puntero igual a direccin de variable).

CURSO_MICROS

P g i n a | 91

Ahora veamos ejemplos donde la forma de asignacin cambia a puntero = puntero. Esto incluye a los arrays porque, recordemos, un puntero siempre puede ser tratado como un array, aunque lo contrario no siempre es posible. En el siguiente programa array1 y array2 se pasan a la funcin prom, la cual devuelve el valor promedio de los elementos del array recibido. Como para ese clculo se necesita conocer la cantidad de elementos que tiene el array, prom recibe dicho valor en el parmetro size. float prom ( int * p, int size ) { int i; float tmp = 0; for ( i=0; i<size; i++ ) // Bucle para contar i desde 0 hasta size-1. tmp += p[i]; // Sumar elemento p[i] a tmp. return ( tmp/size ); // Retornar valor promediado. } void main (void) { int array1[4] = { 51, 14, 36, 78 }; // Un array de 4 elementos int array2[] = { -85, 4, 66, 47, -7, 85 }; // Un array de 6 elementos float avrg; // Una variable tipo float, para decimales avrg = prom (array1, 8); // Ahora avrg debera valer (51 + 14 + 36 + 78 )/8 = 44.75 avrg = prom (array2, 6); // Ahora avrg debera valer (-85 + 4 + 66 + 47 - 7 + 85 )/6 = 18.3333 while( 1 ); // Bucle infinito } Finalmente, veamos un programa donde se utilizan las Cadenas de texto terminadas en nulo. Este programa tiene dos funciones auxiliares: mayus convierte la cadena recibida en maysculas, y lon calcula la longitud del texto almacenado en el array recibido. Ambas funciones reciben el array pasado en un puntero p dado que son compatibles. void mayus( char * p ) { while( *p ) // Mientras carcter apuntado sea diferente de 0x00 { if( ( *p >= a ) && ( *p <= z ) ) // Si carcter apuntado es // minscula *p = *p - 32; // Hacerlo mayscula p++; // Incrementar p para apuntar sig. carcter } } int lon( char * p) { int i = 0; // Declarar variable i e iniciarla a 0. while( *p ) // Mientras carcter apuntado sea diferente de 0x00 { i++; // Incrementar contador. p++; // Incrementar p para apuntar sig. carcter }

CURSO_MICROS

P g i n a | 92

return i; // Retornar i } void main (void) { int L; char song1[20] = Dark Blue; char song2[20] = Staring Problem; char song3[20] = Ex-Girlfriend; /* Obtener longitudes de los arrays de texto */ L = lon(song1); // Debera dar L = 9 L = lon(song2); // Debera dar L = 15 L = lon(song3); // Debera dar L = 13 /* Convertir cadenas en maysculas */ mayus(song1 ); // Es lo mismo que mayus(&song1); // Ahora song1 debera valer DARK BLUE mayus(song2 ); // Es lo mismo que mayus(&song2); // Ahora song2 debera valer STARING PROBLEM mayus(song3 ); // Es lo mismo que mayus(&song3); // Ahora song3 debera valer EX-GIRLFRIEND while(1); // Bucle infinito } En el programa se crean tres arrays de texto de 20 elementos (song1, song2 y song3), pero el texto almacenado en ellos termina en un carcter 0x00. Segn la tabla de caracteres ascii, las letras maysculas estn ubicadas 32 posiciones por debajo de las minsculas. Por eso basta con sumarle o restarle ese valor a un carcter ascci para pasarlo a mayscula o minscula. En ambas funciones el puntero p navega por los elementos del array apuntado hasta que encuentra el final, indicado por un carcter nulo (0x00). Arrays constantes No es que me haya atrazado con el tema, es solo que los arrays constantes son uno de los temas cuyo tratamiento vara mucho entre los distintos compiladores. Veamos en qu. Un array constante es uno cuyos elementos solo podrn ser ledos pero no escritos; tan simple como eso. En principio, para que un array sea constante a su clsica declaracin con inicializacin de un array se le debe anteponer el calificador const. Por ejemplo: const int a[5] = { 20, 56, 87, -58, 5000 }; // Array constante const char vocals[5] = { a, e, i, o, u }; // Array constante const char text[] = Este es un array constante de caracteres; De este modo, los arrays a, vocals y text sern de solo lectura, y sus elementos podrn ser ledos, mas no escritos. Es como si estuviramos frente a una tabla hecha en ensamblador (de PICs) a base de instrucciones retlw. De hecho, los compiladores Mikro

CURSO_MICROS

P g i n a | 93

C, CCS C, Hi-tech C e IAR C, construirn internamente tablas semejantes para representar estos arrays. Si estos arrays constantes van a ser ledos directamente y mientras se utilice la notacin de los corchetes para especificar a cada elemento, todo estar ok. Por otro lado, cada compilador trabaja diferente el paso de arrays constantes a funciones y su compatibilidad con los punteros. Por ejemplo, Hi-tech C soporta magistralmente estos temas. Por otro lado, CCS C no acepta nada, pero ofrece excelentes mtodos alternativos para realizar esas tareas. De modo que ser necesario revisar el manual de cada compilador en particular. En otros compiladores, como MPLAB C18 o BoostC, debe adems aadirse el calificador rom (no perteneciente al C estndar). BoostC solo soporta arrays constantes con datos de 8 bits (de tipo char y compatibles) y su peculiar forma de declarlas es la siguiente. rom char * a = cadena de texto constante en BoostC; rom char * mat = { 20, 56, 87, -58, 50 }; // Array constante rom unsigned char * dec = { 10, 20, 30, 40, 50 }; // Array constante Por lo dems, el acceso a los elementos de estos arrays tiene que seguir siendo mediante ndices y corchetes. En el compilador MPLAB C18, la palabra rom va junto al const. Por supuesto que en los PIC18 los arrays en ROM se implementan con ms eficiencia sin recurrir a los limitados retlw. rom const char * a = cadena de texto constante en MPLAB C18; rom const char a[] = otra cadena de texto constante en MPLAB C18; rom const int mat[5] = { 20, 56, 87, -58, 5000 }; // Array constante rom const long pots[] = {10, 100, 1000, 10000, 100000}; // Array constante El compilador BoostC/C++ Voy a empezar con este compilador ms por una suerte de descarte de los otros productos: IAR C ya no distribuye compiladores para PICs de las familias Baseline y Midrange. La versin demo de CCS C no nos permite usar nuestro PIC16F84A (usar CCS C en el siguiente Mdulo). Mikro C presenta funciones incorporadas cuyos nombres interfieren con los identificadores del programa principal, no soporta funciones reentrantes y tampoco genera un archivo de depuracin-simulacin para trabajar con Proteus (por lo menos hasta la versin 8.0.0, que es la veo en este momento). No pude descargar ni el demo Bytecraft porque me pedan una suerte de invitacn, y como me sent ajeno a la fiesta... FedC tiene un entorno demasiado infantil para mi gusto y tampoco genera archivos de depuracin con Proteus (eso para m es muy importante). Hitech PICC me fascina porque se puede usar el lenguaje C sin tener que preocuparse demasiado por las limitaciones del compilador dado que normalmente lo soporta todo. Increblemente, le encontr algunos errores en la versin 9.60 (la ltima que revis) cuando utilizaba el PIC16F84A; en fin, ser para otra ocasin. As me qued con BoostC; no ser el mejor pero su precio lo compensa. Adems tiene algunas caractersticas que yo considero nos sern tiles para empezar a conocer cmo trabajan los compiladores de alto nivel. De todos modos, no s si sea el enfoque ms adecuado,

CURSO_MICROS

P g i n a | 94

pero tengo pensado ir cambiando de compilador a lo largo del curso; y he considerado a BoostC como un conveniente punto de partida. Cuando mencion el relativo bajo costo de BoostC no quise insinuar que lo tuvieras que comprar. No seor. Este es probablemente el curso ms barato que se pueda encontrar. Al igual que mayora de los otros compiladores, la versin demo de BoostC/C++ permite desarrollar programas que utilicen como mximo 2 k palabras de la memoria de programa en los PICs de la familia Midrange. Como 2 k es el doble de la memoria del PIC16F84A, al menos en ese respecto no tendremos limitaciones. Ahora puedes bajar el compilador BoostC desde su sitio web www.sourceboost.com y a continuacin instalarlo como cualquier programa de Windows. No hay ningn detalle a remarcar en este procedimiento. Al ser ejecutado por primera vez, el IDE de SourceBoost presenta un proyecto de ejemplo llamado interrupt. No voy a hacer una descripcin pormenorizada de este entorno, no solo porque es el IDE ms sencillo y amigable que conozco, sino porque los archivos de ayuda que trae incluidos son bastante ilustrativos. Aparte de las infaltables barras de men y de herramientas (cuyas principales funciones quedarn evidenciadas en lo sucesivo), se distinguen tres reas, tres ventanas que, como en los entornos de los mejores programas de Windows, se pueden mover y colocar en las posiciones que mejor convegan: La ventanita Workspace. Se puede mostrar u ocultar mediante el botn de la barra de herramientas. Es anloga a la ventanita Project Window del MPLAB IDE (solo que ms dcil porque no se anda escondiendo cada vez que maximizamos las otras ventanas;): sirve bsicamente para mostrar y administrar los archivos del proyecto; los archivos contenidos pueden ser visualizados en el editor de cdigo haciendo doble click en sus nombres; mediante su men contentual del botn derecho se pueden aadir otros archivos al proyecto, etc.

CURSO_MICROS

P g i n a | 95

Entorno de Desarrollo Integrado (IDE) de SourceBoost. La ventanita Ouput. Se puede mostrar u ocultar con el botn de la barra de herramientas. Sus funciones son similares a las ofrecidas por la ventana Output del MPLAB IDE: reporta los resultados de la construccin del proyecto, entre otras cosas. El editor de cdigo. Se abre automticamente para mostrar el contenido de algn archivo. Sus capacidades son similares a las de cualquier otro excelente producto: es completamente reconfigurable en cuanto a los colores, tamaos y tipos de fuente del texto, tamao de las tabulaciones, etc.; tiene capacidades de folding; puede mostrar las lneas de cdigo; puede resaltar los smbolos pares como parntesis o corchetes; tiene funcin de autocompletado de palabras definidas al pulsar las teclas ctrl + spacebar; permite duplicar las lneas de cdigo con la combinacin ctrl + d; etc. Muchas de estas caractersticas se pueden configurar o habilitar desde la opcin de men Settings > Editor...

CURSO_MICROS

P g i n a | 96

Capacidad de autocompletado de texto (Ctrl + Spacebar).

Ventana de configuracin del editor de cdigo. Ahora trata de mostrar en el editor de cdigo (con doble click sobre sus nombres en la ventanita Workspace) los archivos del proyecto interrupt cargado inicialmente. Los archivos con extensin .c (y con extensin .h si los hubiera) son los que contienen los cdigos fuente del programa. Los otros (como *.asm, *.lst, *.hex y *.cof) son generados por el compilador. Notars que estos ltimos no se visualizan y la ventanita Output muestra mensajes de Failed to locate file... (No se pudo encontrar el archivo...). Sucede que este proyecto an no est construido. As que hagmoslo presionando el botn Build Project seleccionando el men Project > Build... de la barra de herramientas o

De inmediato vers que dicho botn cambia de icono . No lo vuelvas a tocar hasta que retome su aspecto original. Cuando lo haya hecho, significa que la construccin del proyecto ha terminado y la ventana Output debera mostrar los resultados del proceso exitoso y el reporte de uso de memoria, como se indica en la figura de abajo.

CURSO_MICROS

P g i n a | 97

Ventana Output mostrando los resultados de una construccin exitosa. Como la construccin fue exitosa, puedes intentar abrir otra vez los archivos resultantes del proyecto como *.asm o *.hex. Todos estos archivos se generan por defecto en la misma carpeta donde se encuentra el archivo del proyecto; en este caso en carpeta C:\Archivos de programa\SourceBoost\Samples\C\BoostC. Creacin de un proyecto en BoostC Prctica 1 Creacin de un proyecto en BoostC El proyecto que construimos en la presentacin de BoostC era uno de los ejemplos que traa incluidos el compilador. En esta prctica vamos a crear un proyecto nuevo desde cero. El Circuito Ninguna de las prcticas de este captulo necesita ser implementada fsicamente. Muchas (casi todas) son las mismas que realizamos en el Mdulo 1; solo que en versin C. As no tenemos que preocuparnos ni de armarlos para saber como funcionan en la vida real (porque ya lo hemos visto). Bastar con verlos en un buen simulador como Proteus. Proteus ser especialmente til porque nos permitir apreciar el flujo de ejecucin de un programa en C: sentencia por sentencia, ms que lnea por lnea como en el ensamblador. As que empezaremos con la misma prctica con que empezamos MPLAB IDE y Proteus VSM, con el programa que muestra dos LEDs prendidos que se desplazan desde el centro hacia los costados indefinidamente; un secuencial, un LED flasher o como le quieras llamar.

CURSO_MICROS

P g i n a | 98

El circuito. El cdigo fuente La explicacin de este cdigo se presenta en la subsiguiente seccin. //**************************************************************************** // FileName: LedFlasher.c // Processor: PIC16F84A // Compiler: BoostC //**************************************************************************** #include <system.h> // Incluir este archivo /* La siguiente directiva establece los Bits de Configuracin */ #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON /* La siguiente directiva configura la frecuencia de reloj */ #pragma CLOCK_FREQ 4000000 void main(void) { trisb = 0x00; // Configurar PORTB para salida while(1) // Bucle infinito { portb = 0b00000000; // Sacar 0b00000000 a PORTB delay_ms(200); // Pausa de 200ms portb = 0b00011000; // ... delay_ms(200); portb = 0b00100100; delay_ms(200); portb = 0b01000010; delay_ms(200); portb = 0b10000001; delay_ms(200);

CURSO_MICROS

P g i n a | 99

} } Procedimiento Ve al men Workspace > Stand-Alone Project y escoge New Project... Se nos abrir un cuadro de dilogo para nombrar a nuestro nuevo proyecto. No es necesario escribir la extensin, solo escogemos un nombre prudente y lo guardamos en una carpeta adecuada. (Es siempre recomendable tener cada proyecto en una carpeta diferente.)

Cuadro de dilogo para nombrar el nuevo proyecto. A continuacin se nos presenta la ventanita que pide seleccionar bsicamente la toolsuite a usar (an recuerdas lo que es una toolsuite?). El IDE de SourceBoost puede administrar proyectos con los compiladores BoostC, BoostBasic, C2C, entre otros. Los productos estrella son, por supuesto, BoostC y BoostC++ (que para las prcticas de este curso funcionarn igual); as que seleccionamos cualquiera de ellos. En el rea superior escogemos with empty source file (con archivo fuente vaco) para que no tengamos que aadirlo despus.

Seleccin de la toolsuite a usar. A continuacin estamos de regreso en el IDE de SourceBoost. Podemos ver cmo la ventanita Workspace nos muestra los archivos del proyecto creado como LedFlasher.c y los correspondientes *.asm, *.lst, *.hex y *.map (que an no existen, obviamente).

CURSO_MICROS

P g i n a | 100

Ventana Workspace en primer plano mostrando los archivos del proyecto. Pero algo huele mal aqu: en ningn momento nos pidieron escoger el PIC con el que vamos a trabajar. As que lo tendremos que hacer manualmente. Para ello ve al men Settings > Target y haz lo nico que se puede hacer en la ventanita presentada.

Seleccin del dispositivo. Otra configuracin por la que deberamos haber pasado es la frecuencia del XTAL usado. De nuevo, ve la men Settings > Clock rate. 4000000 significa 4 Mhz.

Establecimiento de la frecuencia del cristal. Ahora s, todo parece estar preparado para que empecemos a codificar y la barra de estado nos lo debera indicar as.

Barra de estado mostrando los parmetros del proyecto.

CURSO_MICROS

P g i n a | 101

Sin ms, abre el archivo principal del proyecto (LedFlasher.c en nuestro caso) en el editor de cdigo clicando dos veces sobre su nombre en la ventanita Workspace.

SourceBoost IDE con el archivo principal del proyecto en el editor de cdigo. Sobre este boceto podemos terminar de escribir el resto del cdigo del programa o se puede aplicar el bendito copiar pegar. Como sea, debera quedar como en la siguiente figura.

CURSO_MICROS

P g i n a | 102

Editor de cdigo con el programa completado. Con el cdigo debidamente escrito, emprendemos la construccin del proyecto como ya sabemos hacerlo: seleccionando el men Project > Build o, ms fcil, presionando el icono de la B gtica. Espera a que termine la construccin. La ventanita Output deber ir mostrndote los procesos llevados a cabo.

Ventana Output con el reporte de la construccin exitosa. Al final, si la construccin fue exitosa (success ... done), la ventanita Output tambin debera enviarnos un reporte del uso de la memoria (Memory Usage Report) como se muestra en la imagen de arriba. All la ventanita Output est desbocada de su lugar habitual solo para que los mensajes presentados se puedan apreciar mejor. Para comprobar la correcta construccin del proyecto puedes visualizar en el editor de cdigo los archivos resultantes como *.asm, *.lst o el ansiado *.hex.

CURSO_MICROS

P g i n a | 103

Editor de cdigo mostrando el archivo *.hex. O puedes puedes abrir la misma carpeta del proyecto. Entre otros tantos tambin podrs hallar all el archivo *.cof, para la simulacin con Proteus VSM.

La carpeta del proyecto conteniendo todos los archivos del proyecto construido. En cambio, si no tuviste suerte, la ventanita Output te mostrar los errores encontrados. Por ejemplo, un error tpico de los noveles suele ser el famoso ... missing semicolon (... falta punto y coma). Al puro estilo de la ventanita Output del MPLAB IDE, haz doble click en la lnea de error para resaltar la lnea donde se encuentra ese error.

CURSO_MICROS

P g i n a | 104

La siguiente imagen muestra la accin descrita, con la ventanita Output desencajada de su lugar habitual para una mejor apreciacin del error indicado.

Correccin de los errores hallados. As se deben ir corrigiendo los errores y volviendo a construir el proyecto hasta que todo salga bien. Cuando hayamos terminado con esta etapa podremos pasar a simular el programa o a grabar el PIC directamente. SourceBoost IDE incluye un nada desdeable simulador, parecido al MPLAB SIM. De hecho, todos los simuladores son muy parecidos, as que no voy a entrar en detalle sobre ese tema porque ya me cans de repetir esos conceptos de step over, step into, etc., porque en ese respecto basta con la ayuda del compilador, y porque si tienes Proteus VSM... Ya sabes, no hay simulador que se le compare. (A propsito, los proyectos de BoostC tambin se pueden construir en el MPLAB IDE y, por tanto, se pueden simular con el MPLAB SIM y depurar con el MPLAB ICD.) Quienes por alguna razn se interesen por el simulador de SourceBoost pueden arrancarlo o detenerlo presionando el botn plugins disponibles en el men Plugins. de la barra de herramientas. Hay varios

En la siguiente capura de pantalla la simulacin muestra el cdigo ejecutado en dos ventanas: una con el cdigo en C y la otra con el correspondiente cdigo en ensamblador. La vista de cdigo ensamblador se puede alternar con el botn de la barra de herramientas o con la opcin de men View > Code Bar y seleccionando la ficha Assembly. Su contenido puede resultar particularmente interesante para quienes tengan la curiosidad de ver cmo realiza su trabajo el compilador BoostC, ya que muestra sentencia por sentencia de cdigo C con sus correspondientes instrucciones assembler. En realidad, lo mostrado en esta ventana es el contenido del archivo *.casm del proyecto. Por eso tambin se puede examinar su contenido sin necesidad de estar en modo simulacin mediante el botn ya indicado.

CURSO_MICROS

P g i n a | 105

Simulacin del programa en el entorno de SourceBoost. Por otro lado, si ests entre los afortunados que cuentan con Proteus, sabes que el archivo de depuracin/simulacin a cargar en el PIC es el que tiene extensin .cof (puede varar en otros compiladores). La ventanita PIC CPU Variables nos muestra los registros y las variables utilizados en el programa. Eso es bueno porque no pasaba as con los archivos *.cof generados por el MPLAB. Sin embargo, an no es suficiente (al menos hasta la versin que utilic, la 6.87) por dos razones: Primero, en todo momento aparecen todas las variables utilizadas por el programa. Sera preferible que solo se apreciaran las que son modificadas actualmente para distinguir fcilmente las variables que tienen el mismo nombre (comn en los programas en C). Y Segundo, tambin aparecen los nombres de todos los registros especiales del PIC. Lo ideal sera que solo se vieran los registros utilizados directamente por el cdigo C (para otros casos podramos sacar la ventana Watch). Por ejemplo, en la siguiente toma de imagen notamos la presencia de los registros tmr0, eedata, indf, etc., que nada tienen que ver con nuestro cdigo C. (Si vamos a buscar defectos en cada compilador que usamos, te aseguro que los vamos a encontrar en todos.)

CURSO_MICROS

P g i n a | 106

Simulacin del programa en Proteus VSM. Con simulacin o sin ella, todo proyecto (no me refiero a este ejemplo precisamente) debera terminar con el programa debidamente grabado en el microcontrolador. Claro que no vamos a repetir ahora las cosas que aprendimos en el Mdulo 1; es solo que me llam la atencin la posibilidad de SourceBoost de poder invocar a los programadores genricos como WinPic800. Para que el botn de la barra de herramientas te permita acceder al WinPic800 con el reciente archivo *.hex directamente cargado y listo para ser grabado, debes abrir la ventana del men Settings > Option... y en el campo Programmer de la ficha Tools establecer la ruta del WinPic800 (o IC-PROG o el software que uses).

CURSO_MICROS

P g i n a | 107

Configuracin de WinPic800 como software de programacin. Descripcin del programa Los archivos de dispositivo suelen contener los nombres de los registros, de los bits de los registros y de los Bits de Configuracin del microcontrolador. Recordemos que en assembler los tenamos que incluir explcitamente, por ejemplo poniendo #include < p16f84a.inc>. Al trabajar con los compiladores en cambio se incluye un archivo genrico, system.h en el caso de BoostC, el cual se encargar de invocar a todos los dems archivos necesarios para la construccin del proyecto, entre ellos el archivo del PIC que se haba seleccionado anteriormente desde el IDE (men Settings > Target...). #include <system.h> El archivo de dispositivo para este proyecto ser PIC16F84A.h. Para examinar ste o cualquier otro archivo que es llamado por system.h, puedes seguirles la pista abrindolos en el editor mediante el men contextual despus de situar el cursor en el nombre del archivo. Practica eso cuando tengas tiempo. Lamentablemente, los archivos de dispositivo en BoostC usan los nombres de los registros en minsculas y los nombres de los bits de registros en maysculas. se sera el primer escollo a superar. Pasemos a otros puntos: directiva #pragma hace lo que dice su comentario. Los nombres de las mscaras de los Fusess de configuracin (_PWRTE_ON, etc.) son los mismos que usa el ensamblador. As que no debera significar mayor novedad. Si no los recuerdas puedes chequear el archivo del dispositivo. La directiva #pragma CLOCK_FREQ 4000000 no es nada parecido a lo que conocemos. Indica la frecuencia del XTAL que utilizar nuestro PIC. El compilador necesita saberlo para poder generar adecuadamente las funciones delay_xx utilizadas en el programa. Obviamente, en nuestro caso 4000000 significa que usaremos un XTAL de 4 MHz. Habrs recordado que esta frecuencia tambin se puede establecer en el entorno de BoostC. De las dos formas, esta directiva tiene mayor prioridad. Ahora expliquemos el cdigo ejecutable. En un programa puede haber varias funciones y con cualesquiera nombres, pero nunca debe faltar la funcin main (principal, en ingls). Como habrs podido apreciar en su simulacin, ah es donde empieza a ejecutarse el programa y, de hecho, el control de programa no debera salir de all. void main(void) { // Cuerpo de la funcin main // ... } En un programa C para ordenador cuando termina la funcin main el control pasa al sistema operativo. En un programa para microcontrolador no hay S.O. As que no debera terminar. Por eso se suele poner algn bucle infinito para que el programa se quede dando vueltas para siempre. Por ejemplo:

CURSO_MICROS

P g i n a | 108

while(1) // Bucle infinito { // Bloque (o cuerpo) del bucle while // ... } ste es un bucle while, que ejecutar un bloque de instrucciones mientras la expresin entre los parntesis indicados sea verdadera. En este caso, dicha expresin es 1. Una expresin de este tipo es verdadera si es diferente de 0 y como 1 siempre ser diferente de 0, el cuerpo del bucle se ejecutar una y otra vez, indefinidamente, forever, hasta el infinito... La sentencia de llamada a funcin delay_ms(200) genera un delay de 200 ms (qu descubrimiento!). Por desgracia, el valor mximo admisible es de 255. Eso lo puedes comprobar revisando su prototipo de funcin en el archivo boostc.h, abrindolo de la forma indicada antes. delay_ms(200); // Pausa de 200ms

Habrs notado que para acceder a un registro ya no es necesario cambiar a su banco de ubicacin. Todas las labores de cambio de banco y de pgina las realizar el compilador. Tampoco hace falta enmarcar en una seccin teorica aparte el significado de los prefijos 0x y 0b para descubrir que indican nmeros en formato hexadecimal y binario, respectivamente. Los nmeros sin prefijos son tratados por el compilador como decimales, como lo entenderamos nosotros; por ejemplo, 1000, +200, 0, -35, etc. Prctica 2 En ensamblador solamos redirigir el flujo de un programa utilizando instrucciones de salto como el goto. Aunque en C tambin existe una sentencia goto, que hace casi lo mismo, su uso est sensurado por las buenas prcticas de programacin. Si algn programador respetable viese un goto injustificado en tu programa, se reira de l. Las formas de hacer estas operaciones es mediante las sentencias de control de programa como if (y similares), while, for, etc. El programa enciende un LED ms de los ocho conectados al puerto B cuando se presiona un botn, y apaga otro LED ms cuando se pulsa otro botn.

CURSO_MICROS

P g i n a | 109

El Circuito

El circuito. El cdigo fuente

//**************************************************************************** // FileName: OnOffLEDs.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Operadores, sentencia if, bucle while. //**************************************************************************** #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 void main() { char t = 0; // Declarar variable t e inicializarla a 0 trisa.4 = 1; // Pin RA4 entrada trisa.3 = 1; // Pin RA3 entrada trisb = 0x00; // PORTB como salida portb = 0x00; // Limpiar PORTB while(1) // Bucle infinito { if (porta.4==0) // Si RA4 vale 0, ejecutar sig, bloque { t <<= 1; // Desplazar t a la izquierda t |= 0x01; // Setear bit 0 de t portb = t; // Colocar en PORTB delay_ms(40); // Delay antirrebote while(porta.4==0) // Mientras RA4 valga 0, esperar

CURSO_MICROS

P g i n a | 110

continue; } if (porta.3==0) // Si RA3 vale 0, ejecutar sig. bloque { t >>= 1; // Desplazar t a la derecha t &= 0x7F; // Limpiar bit 7 de t portb = t; // Colocar en PORTB delay_ms(40); // Delay antirrebote while(porta.3==0) // Mientras RA3 valga 0, esperar continue; } } } Descripcin del programa El cuerpo de la sentencia while(1) tiene dos sentencias if. Cada sentencia if a su vez tiene varias sentencias ms. As son los programas en C, sentencias conteniendo sentencias y ms sentencias. Las sentencias if del programa comparan los bits RA4 y RA3 de PORTA con 0. Valdrn 0 cuando los botones en dichos pines estn pulsados. Claro que sabemos cmo funciona todo eso. Mi punto era este: podemos leer una expresin con el operador == diciendo igual; por ejemplo: if (porta.4==0) // Si RA4 vale 0, ejecutar sig, bloque

se leera si el bit 4 de PORTA es igual a 0, entonces.... Sin embargo, no debemos confundirlo con el operador =, que transfiere el valor de un dato, variable o una expresin a otra variable. Suele ser un error frecuente en los diseadores noveles no detectable fcilmente porque el compilador no dar errores. Por ejemplo, esta expresin tambin es totalmente vlida: if (porta.4 = 0) // Si RA4 igualado a 0 es diferente de 0...

Significa que primero se asigna el valor 0 al bit 4 de PORTA y despus se comprueba si ese valor es verdadero. Por supuesto que nunca lo ser. Sigamos con ms operadores. t <<= 1; t |= 0x01; // Desplazar t a la izquierda // Setear bit 0 de t

Aqu vemos un ejemplo del uso de operadores compuestos. Por si an no te acostumbras a su forma, puedes desenvolver imaginariamente las sentencias de esta forma: t = t << 1; t = t | 0x01; // Desplazar t a la izquierda // Setear bit 0 de t

Supongo que ahora est ms claro. t << 1 significa desplazar la variable t una posicin a la izquierda y t | 0x01 significa aplicar la operacion OR binaria entre la variable t y la

CURSO_MICROS

P g i n a | 111

constante 0x01. Para ms informacin sobre estos temas puedes releer la seccin Los operadores. En realidad, en BoostC tambin es posible modificar un bit en una variable mediante el operador punto (.) o utilizando una macro como set_bit o clear_bit. As por ejemplo, t | 0x01 es equivalente a cualquiera de estas formas:

t.0 = 1; set_bit(t, 0);

// Setear bit 0 de t // Setear bit 0 de t

set_bit es una macro definida en el archivo boostc.h y si alguna ves lo llegas a examinar, vers que tambin est implementada con el operador |. En boostc.h tambin estn las macros clear_bit y toggle_bit, que limpian o permutan un bit de una variable cualquiera. Pero como este operador punto no es parte del C estndar y como boostc.h es propio de BoostC, yo prefiero emplear los operadores or binaria (|), and binaria (&) y xor binaria (^) para realizar esas operaciones. Espero que no me maldigas por eso. Finalmente, asumo que ya sabes para qu ponemos esos delays antirrebotes y por qu tenemos que esperar mientras RA4 valga 0. while(porta.4==0) // Mientras RA4 valga 0, esperar continue; El cuerpo de este bucle while se ejecutar mientras el bit 4 de PORTA valga 0 (mientras el botn en cuestin est pulsado). continue es una sentencia que salta al lugar despus de la ltima sentencia del bloque de while. Qu significa todo esto?, cul bloque? Significa que mi continue est de adorno ;) ya que el bloque de este while solo tiene una sentencia (el mismo continue). En consecuencia, bien pude haber escrito esa construccin as: while(porta.4==0); // Mientras RA4 valga 0, esperar

que es su presentacin ms habitual, o de esta otra forma: while(porta.4==0) { } // Mientras RA4 valga 0, esperar Yo acostumbro poner el continue solo para que en la depuracin o simulacin del cdigo se vea mejor :) Tanto para eso!... Prctica 3 En esta prctica seguiremos usando las sentencias selectivas (if else en este caso) y tambin veremos la forma de manejar los arrays unidimensionales para trabajar con matrices de datos en operaciones que en ensamblador las resolvamos mediante las tablas (de bsqueda).

CURSO_MICROS

P g i n a | 112

El programa visualiza un contador hexadecimal que avanza de 0 a 15 en modo ascendente si un switch conectado al pin RA2 marca un 1 lgico. El contador avanza en modo descendente si el switch marca 0 lgico. El Circuito

El circuito. El cdigo fuente

//**************************************************************************** // FileName: HexCounter.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Sentencia if-else, array constante en BoostC. //**************************************************************************** #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 #define swich porta.2 // switch esta conectado a RA2 #define Top 12 // Cima de conteo es 12 = 0x0C rom char * Code7seg = // Array constante almacenado en rom (FLASH) { 0b00111111, // 7-segment code of 0. Format: 0bxgfedcba , x=dont care 0b00000110, // 7-segment code of 1. ... 0b01011011, // 7-segment code of 2 0b01001111, // 7-segment code of 3 0b01100110, // 7-segment code of 4 0b01101101, // 7-segment code of 5 0b01111101, // 7-segment code of 6 0b00000111, // 7-segment code of 7

CURSO_MICROS

P g i n a | 113

0b01111111, 0b01101111, 0b01110111, 0b01111100, 0b00111001, 0b01011110, 0b01111001, 0b01110001, };

// 7-segment code of 8 // 7-segment code of 9 // 7-segment code of A // 7-segment code of B // 7-segment code of C // 7-segment code of D // 7-segment code of E // 7-segment code of F

void main() { unsigned char Count; trisb = 0x00; trisa.2 = 1;

// Declara variable Count

// Salida para display // Pin RA2 entrada

if(swich) Count = 0; // Si swich vale 1, iniciar desde 0 (modo asc) else Count = Top; // De lo contrario, iniciar desde Top (modo desc) while(1) // Bucle infinito { portb = Code7seg[Count]; delay_ms(250); delay_ms(250); if (swich) // Si swich vale 1, ejecutar sig. bloque { if (Count < Top) // Si Count < Top Count++; // Incrementar Count else Count = 0; // De lo contrario, reiniciar Count } else // De lo contrario, ejecutar sig. bloque { if (Count) // Si Count > 0 Count--; // Decrementar Count else Count = Top; // De lo contrario, reiniciar Count } } }

Descripcin del programa Hay cuatro temas que voy a tocar sobre este programa. El primero es la directiva #define. Nosotros ya sabemos cmo funciona: sustituye el identificador por el texto subsiguiente. En C existe adems la posibilidad de realizar construcciones mucho ms complejas, pero la forma tpica de usarlas es como ya sabemos.

CURSO_MICROS

P g i n a | 114

Pasando al siguiente punto: la sentencia if else ejecuta uno de dos bloques de sentencias, segn la condicin verdadero-falsa de una expresin. Es muy fcil entender esta condicin en una expresin como: if (Count < Top) // Si Count < Top

La expresin entre parntesis ser verdadera cuando Count sea menor que Top. Qu novedad!... Ahora bien, cmo se evala esta expresin if (swich) o esta otra? if (Count) // Si Count > 0 // Si swich vale 1, ejecutar sig. bloque

Con qu valores se comparan swich y count? Bien, en C una expresin no relacional (con operadores de comparacin como >, <, ==, etc.) ser verdadera si es diferente de cero; o, dicho de otra forma, solo ser falsa si vale 0 y en cualquier otro caso ser verdadera. Claro que para evitar las dudas estas sentencias bien se pudieron haber escrito empleando los operadores igual (==), diferente (!=) u otros, as: if (swich != 0) // ... if (Count > 0) // Si swich es diferente 0,... // Si Count > 0,...

Pero a los programadores de C nos da pereza tipear ms cdigo de lo necesario. Quiz este estilo te disguste en un inicio pero pronto estars de acuerdo con nosotros. No lo olvides: una expresin no relacional simple es verdadera si vale diferente de 0 y es falsa cuando vale 0. El tercer tema era el array Code7seg. Esta forma de declarar los Arrays constantes, con la palabra reservada rom y el asterisco, es propia del compilador BoostC; as que no le busquemos mayor lgica porque va a variar en otro compilador. rom char * Code7seg = // Array constante almacenado en rom (FLASH) { 0b00111111, // 7-segment code of 0. Format: 0bxgfedcba , x=dont care // ... 0b01110001, // 7-segment code of F }; Nota que los elementos deben ir separados por comas; de modo que no es necesario que vaya uno por cada lnea. Lo escrib as solo para que se distingan mejor. Lo que no debera variar en ningn compilador C es el acceso a los elementos del array, mediante corchetes e ndices. As la sentencia portb = Code7seg[Count]; enva al puerto B el elemento nmero Count del array Code7seg. En C el primer elemento siempre es el 0.

CURSO_MICROS

P g i n a | 115

Finalmente, pasemos al cuarto punto: el tipo de dato ideado para trabajar con nmeros enteros en el C estndar es int (entero de 16 bits con signo, suficiente para alojar la gran mayora de los datos procesados en un programa). Sin embargo, en el mundo de los Cs la RAM no es un recurso que se caracterice por abundar. Es por eso que se muchas veces se suele usar el tipo char, que fue inicialmente concebido para almacenar caracteres (characters) ascii (de 8 bits), como a, 5, #, etc. A este tipo se le han aadido los especificadores signed (con signo) y unsigned (sin signo) para formar los tipos: signed char establece variables enteras de 8 bits con signo (desde 128 hasta +127). unsigned char establece variables enteras de 8 bits sin signo (desde 0 hasta 255).

char tambin se puede usar para almacenar nmeros, pero funcionar ambiguamente, a veces como signed char y otras como unsigned char, segn el compilador entre otras cosas. Si los datos usados en el programa no requieren formato, se podrn usar los tres tipos indistintamente sin que funcione mal y sin que el compilador se d cuenta. Sin embargo, si las variables van a trabajar como nmeros en operaciones aritmticas o relacionales de comparacin, ser necesario aplicarles el signed o unsigned respectivo. PRCTICA 4 Descripcin del programa Este programa tiene dos funciones: main (infaltable) y Flash, para la que no fue necesario escribir su prototipo de funcin porque la puse encima de main. Tratemos esta extraa construccin: for(;;) // Bucle for sin condiciones (infinito) { // Bloque o cuerpo del bucle for // ... } El bucle for ejecuta un bloque de sentencias segn ciertas condiciones que contiene entre los parntesis. En este programa esas condiciones no existen, lo que para el compilador significa que es un bucle infinito, justamente igual que nuestros anteriores while (1). Se suele usar una u otra forma indistintamente. Ahora veamos los bucles for que s tienen lmites, pero antes quiero que sepas porque uso for y no los bucles while o do-while, que en un inicio pueden ser los ms simples. Cuando uno tiene un enunciado que describe alguna forma de conteo, de inmediato debe pensar en el bucle for. As, cuando yo entend que para que el LED se desplace de uno a otro lado debe recorrer 8 posiciones, de inmediato imagin un bucle de la forma for(i=0; i<8; i++).

CURSO_MICROS

P g i n a | 116

t = 0b00000001; // Inicializar t for(i=0; i<8; i++) // Bucle que cuenta desde 0 hasta 7 { Flash(t); // Llamar funcin Flash t <<= 1; // Desplazar t una posicin a la izquierda } Y ah lo tienes. Lo dems es solo acomodar el cdigo para que el LED se desplace una posicin en cada ciclo. La otra sentencia for, para desplazar el LED hacia la derecha, funciona anlogamente. En ambos bucles for de main el LED debe parpadear 5 veces en cada ciclo. As que, para no redundar en cdigo, pens en una funcin aparte que hiciera esa tarea. void Flash(char dato) { unsigned char i; // Declarar variable i for(i=0; i<5; i++) // Bucle que cuenta desde 0 hasta 4 (5 ciclos en total { portb = dato; // Dado a PORTB delay_ms(30); // Pausa portb = 0; // Limpiar PORTB delay_ms(30); // Pausa } } La funcin Flash debe mostrar el valor de t en PORTB intermitentemente, 5 veces segn el enunciado. As que pens automticamente en otro for, ahora de la forma for(i=0; i<5; i++). (Resulta hasta entretenido codificar as.) Ahora bien, como la funcin Flash no puede acceder a t dado que es variable local de main, ha tenido que implementada para que reciba su valor como parmetro de entrada en dato. De ese modo, al llamar a Flash con la sentencia Flash(t); // Llamar funcin Flash

El valor de la variable local t se copia a la variable local dato. Para ms informacin sobre el paso de parmetros ir a Funciones con parmetros (por valor). Habrs notado que en la funcin Flash hay otra variable i. sta no tiene ninguna relacin con la i de la funcin main. Ambas son variables locales independientes una de la otra porque pertenecen a sus respectivas funciones. Para ms informacin al respecto pueder releer la seccin Variables locales y variables globales. Debo aclarar que el procedimiento descrito es la forma como disee el programa. Es solo uno de los tantos algoritmos que se pueden idear para cumplir con el enunciado planteado al inicio. Por ejemplo, al examinar el cdigo de main podemos ver que bien se pudo haber escrito de esta otra forma, ms compacta: void main() { unsigned char i; trisb = 0x00; for(;;)

CURSO_MICROS

P g i n a | 117

{ for(i=0; i<8; i++) Flash(0x01<<i) for(i=0; i<8; i++) Flash(0x80>>i) } } En general, siempre podrs encontrar ms de un algoritmo alternativo para cualquier tarea. Una cosa es escribir un cdigo que cumpla con un determinado propsito y otra cosa es hacerlo de una forma ms elegante. Hay quienes pueden escribir poesa con sus cdigos. As como no basta con que una persona nazca con el talento, sino que debe de ejercitarlo para convertirse en poeta, as tambin la nica forma de desarrollar tu kung fu es practicar y practicar, y estudiar los buenos cdigos de otros programadores. Para terminar, una vez acabado un programa y probado su correcto funcionamiento, viene la etapa de optimizacin. La palabra optimizacin, tiene bsicamente dos formas: de cdigo, para que el tamao de memoria utilizada sea el menor posible, y de velocidad de ejecucin, para que el cdigo resultante la ejecute el PIC con la mayor velocidad. En general ambos requerimientos se contraponen: es muy raro obtener un programa que se compile en el menor cdigo hex y que al mismo tiempo sea ms rpido de ejecutar. Por ejemplo, la alternativa a la funcin main presentada arriba es ms compacta pero genera mayor cdigo y tambin se ejecuta en mayor tiempo. En este programa, claro est, eso ni se nota ni interesa; tal vez en otros diseos s El Display LCD Pines del LCD Introduccin Este captulo est dedicado a los LCDs alfanumricos con controlador Hitachi HD44780 o compatible, es decir, la mayora. Hay diversas firmas, como Optrex, Sharp, Crystalfontz America, Tianma, etc., que producen muchsimos LCDs de este tipo. Los hay desde 1 a 4 lneas, desde 8 a 40 letras por lnea, algunos con iluminacin de fondo, con diferente tecnologa de fabricacin, etc. Dada la compatibilidad en el control de todos ellos, la eleccin de un modelo en particular queda a tu cargo. El LCD utilizado en este curso es de 2 lneas, de 16 letras cada una. Si bien es necesario conocer un dispositivo para sacerle el mximo provecho, en primera instancia a la mayora de los aficionados solo le interesa ponerlo en prctica aunque sea de forma limitada. Si eres uno de ellos, y por el momento quieres ahorrarte algo de tiempo, puedes saltar a la seccin Interface de un Display LCD.

CURSO_MICROS

P g i n a | 118

Pines del LCD Nmero Smbolo de Pin 1 Vss 2 Vcc o Vdd 3 Vee o Vo 4 RS 5 R/W 6 E 7...14 DB0...DB7 Pines del LCD. Algunos LCDs con iluminacin disponen de dos pines adicionales para encenderla. Aun as, los 14 pines aqu citados siempre deberan coincidir. Nombre de seal DB0 - DB7 E R/W Funcin 8 lneas de bus de datos. Para transferencia bidireccional de datos entre el C y el mdulo LCD. DB7 tambin se puede usar como bit busy flag. En operacin de 4 bits solo se usa el nibble alto. Enable Seal de inicio de operacin de lectura/escritura. Seal para seleccionar operacin de lectura o escritura. 0 : Escribir en LCD 1 : Leer de LCD Register Select 0 : Registro de comandos (escritura). : Busy flag + puntero de RAM (lectura). 1 : Registro de datos (escritura, lectura). Acceso a DDRAM o CGRAM. Ajuste de contraste del LCD. Vee = GND es mximo contraste. Alimentacin = +5 V tpicamente. Alimentacin = 0 V (GND).

RS

Vee o Vo Vdd o Vcc Vss

Memorias del LCD CGROM - Character Generator ROM Es la zona de memoria donde se encuentran grabados los patrones de todos los caracteres que puede visualizar el LCD de fbrica. Tiene grabados cerca de 200 (vara mucho) tipos de caracteres de 57 puntos (lo ms comn) o 32 caracteres de 510 puntos. Este ltimo modo es raramente usado porque no todos los modelos lo soportan.

CURSO_MICROS

P g i n a | 119

Tabla standard de caracteres de la CGROM. DDRAM - Display Data RAM La DDRAM almacena los cdigos de las letras que se visualizan en la pantalla del LCD. Tiene capacidad de 80 bytes, un byte por carcter si la fuente es de 57 puntos. Observa que no siempre se podrn visualizar los 80 caracteres. Por ejemplo, si quisiramos mostrar el mensaje Hello en la pantalla, deberamos enviar a la DDRAM los cdigos ascii de cada letra de esa palabra. El controlador interno del LCD tomar esos cdigos para buscar en la CGROM sus correspondientes patrones de visualizacin y luego los mostrar en la pantalla. La siguiente figura muestra la correspondencia entre las locaciones de la DDRAM y las posiciones de las letras que vemos en la pantalla de un LCD de 2 lneas, particularmente de uno de 216. Fjate en que los 80 bytes de la DDRAM se deviden en dos sectores de 40 bytes, un sector por lnea, as: Lnea 1, con sector de DDRAM desde 0x00 hasta 0x27. Lnea 2, con sector de DDRAM desde 0x40 hasta 0x67.

CURSO_MICROS

P g i n a | 120

Por lo tanto, podemos entender que siempre tenemos un LCD virtual de 240; aunque solo podamos ver 8, 16 20 letras por cada lnea. Los otros datos escritos en la DDRAM permanecen all aunque no se visualicen.

Posiciones en DDRAM de las letras de la pantalla (nmeros en hexadecimal). CGRAM - Character Generator RAM La CGRAM es una RAM de 64 bytes donde el usuario puede programar los patrones de nuevos caracteres grficos, ya sean de 57 puntos (hasta 8 caracteres) o de 510 puntos (hasta 4 caracteres). Este tema lo detallar en la prctica final. El Puntero de RAM Llamado tambin Address Counter, es un registro que sirve para acceder a las memorias RAM del LCD. Por ejemplo, si el Puntero de RAM vale 0x00, accedemos a la locacin de DDRAM (o CGRAM) de esa direccin. Ahora bien, solo hay un puntero de RAM que trabaja con las dos RAMs del LCD, y para saber a cul de ellas accede actualmente debemos ver la instruccin enviada ms recientemente. Las instrucciones Clear Display, Return Home y Set DDRAM Address designan el Puntero de RAM a la DDRAM, mientras que Set CGRAM Address lo designa a la CGRAM. Afortunadamente, en la gran mayora de los casos, el Puntero de RAM estar apuntando a la DDRAM. Adems, en este caso viene a representar la posicin del cursor (visible o no) del LCD en la pantalla. Set instrucciones del display LCD Es el controlador interno HD44780 (u otro) del LCD quien ejecutar las operaciones de mostrar las letras en la pantalla, mover el cursor, desplazar el contenido de la pantalla, etc. Lo que nos toca a nosotros es enviarle los cdigos de esas operaciones. A continuacin, un resumen.

CURSO_MICROS

P g i n a | 121

Instrucciones Clear Display Return Home Entry Mode Set Display ON/OFF Control Instrucciones Cursor or Display de comando Shift Function Set

Cdigo RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 DL 0 0 0 1 0 0 1 D 0 1 I/D C 1 S B

S/C R/L N F

Set CGRAM Address 0 0 Set DDRAM Address 0 0 Read Busy Flag 0 1 & RAM Pointer Write to CGRAM 1 0 Instrucciones or DDRAM de datos Read from CGRAM 1 1 or DDRAM

Puntero de RAM (CGRAM)

Puntero de RAM (DDRAM)

BF Puntero de RAM (DDRAM o CGRAM) Escribir dato Leer dato

Conviene saber que las instrucciones Clear Display y Return Home tienen un tiempo de ejecucin de cerca de 1.52 ms. Las dems toman algo de 40 s. El LCD cuenta con dos registros internos principales, que dividen, grosso modo, las instrucciones en de datos y de comando. Poniendo el pin RS = 1 accedemos al registro de datos y mediante l a cualquier locacin de la DDRAM o CGRAM, para operaciones de lectura y escritura de datos. Con RS = 0 accedemos al registro de comandos para escribir instrucciones de control del LCD (Clear Display, Function Set, etc.). En el caso de una lectura, obtenemos un dato particular que contiene el valor del puntero de RAM junto con el bit Busy flag. Clear display: 0 0 0 0 0 0 0 1 Limpia toda la pantalla del LCD. Tambin retorna el cursor a su posicin inicial (cima izquierda), esto es, designa el puntero de RAM a la direccin 0x00 de la DDRAM. Return home: 0 0 0 0 0 0 1 x Regresa el cursor a su posicin inicial pero sin alterar el texto del display, es decir, solo designa el puntero de RAM a la direccin 0x00 de la DDRAM. Entry mode set: 0 0 0 0 0 1 I/D S Establece el modo de incremento o decremento y modo de desplazamiento del LCD.

CURSO_MICROS

P g i n a | 122

I/D = 1: El puntero de RAM se incrementa en 1 despus de leer o escribir un dato. As accedemos automticamente a la siguiente locacin de DDRAM o CGRAM. Si es DDRAM, este puntero representa la posicin del cursor en la pantalla y el incremento significa su avance a la derecha. I/D = 0: El puntero de RAM se decrementa en 1 despus de leer o escribir un dato. S = 1: Si se escribe un nuevo dato de carcter en el LCD, entonces el display entero se desplaza a la derecha cuando I/D = 0 o a la izquierda cuando I/D = 1. S = 0: El display no se desplaza luego de escribir en la DDRAM. Esto es lo usual.

Display on/off control: 0 0 0 0 1 D C B Prende o apaga el Display, el Cursor y la funcin Blink del cursor. D = 1: El display se prende. D = 0: Apaga el display. (No significa que los datos de las RAMs se vayan a borrar.) C = 1: Despliega el cursor. C = 0: No despliega el cursor B = 1: La letra indicada por el cursor parpadea. B = 0: La letra no parpadea.

Cursor or display shift: 0 0 0 1 S/C R/L x x Desplaza el cursor o el display a la derecha o la izquerda sin escribir o leer datos. S/C R/L Operacin 0 0 Mueve el cursor a la izquierda (puntero de RAM se decrementa en 1) 0 1 Mueve el cursor a la derecha (puntero de RAM se incrementa en 1) 1 0 El Cursor y el display entero se desplazan a la izquierda 1 1 El Cursor y el display entero se desplazan a la derecha Function set: 0 0 1 DL N F x x Configura la longitud del bus de datos, el nmero de lneas y el tipo de fuente. DL = 1 : La interface con el LCD es mediante un bus de datos de 8 bits. DL = 0 : La interface con el LCD es mediante un bus de datos de 4 bits. N = 1: Configura un display de 2 lneas. N = 0: Configura un display de 1 lnea. F = 0: Fuente de carcter de 57 puntos.

CURSO_MICROS

P g i n a | 123

F = 1: Fuente de carcter de 510 puntos.

Set DDRAM address: 1AAAAAAA Designa el puntero de RAM a la nueva direccin AAAAAAA de la DDRAM. Digamos que sirve para controlar la posicin del cursor del LCD. Ejemplo, para escribir un texto en la segunda lnea del display (que tiene direccin inicial 0x40), primero habra que enviar el comando Set DDRAM Address con el nmero 0x40 en el parmetro AAAAAAA. Set CGRAM address: 01AAAAAA Designa el puntero de RAM a la nueva direccin AAAAAAA de la CGRAM. Read Busy Flag & RAM Pointer: BF AAAAAAA Leer bit Busy Flag (BF) y el valor del puntero de RAM. BF = 1 indica que una operacin interna est en progreso. El LCD no aceptar una nueva instruccin hasta que BF sea 0. El valor de AAAAAAA ledo representa el valor del puntero de RAM. Es posible prescindir del bit BF. Para ello debemos esperar el tiempo adecuado antes de enviar la siguiente instruccin. Write data to CGRAM / DDRAM: DDDDDDDD Escribe el dato de 8 bits DDDDDDDD en la DDRAM o CGRAM, dependiendo de cul de las dos est siendo direccionada actualmente. Despus de la escritura el puntero de RAM se incrementa o decrementa, segn se haya configurado el display. Ver instruccin Entry Mode Set. Read data from CGRAM / DDRAM: DDDDDDDD Lee un dato de 8 bits de la DDRAM o CGRAM, dependiendo de cul de ellas est siendo direccionada actualmente. Despus de la lectura el puntero de RAM se incrementa o decrementa en uno, segn la configuracin del display. Ver instruccin Entry Mode Set. Inicializacin del LCD Los LCDs tienen un circuito interno de reset que lo inicializa automticamente tras alimentar el LCD. Lo cierto es que la autoinicializacin no siempre es fiable. Por eso existe la inicializacin por software, que permite una completa configuracin de los parmetros del LCD. Su defecto es que es bastante extico (Un poco ms y nos piden que bailemos tap:). Se constituye de una serie de pasos que, por si fueran poco, varan de acuerdo con la interface de 4 u 8 bits a usar y con el empleo o no del bit Busy Flag. El siguiente flowchart corresponde a la inicializacin del LCD para operar con interface de 4 bits y usando el bit busy flag.

CURSO_MICROS

P g i n a | 124

Inicializacin por software del LCD con interface de 4 bits. Interface de un Display LCD Aunque los LCDs parezcan simples de usar, para bien o para mal sus caractersticas abren puertas a diversos modos de interface. Aqu, algunos puntos de consideracin. Bus de datos. Estos LCDs ofrecen la posibilidad de ser controlados utilizando los 8 bits de su bus de datos o solo 4. Un modo de operacin del LCD (con ventajas y desventajas) le permite trabajar sin conectar el pin R/W al C. En ese modo el pin R/W siempre debe plantarse a tierra.

CURSO_MICROS

P g i n a | 125

Al igual que los PICs, los LCDs estn fabricados con tecnologa CMOS, lo que deriva en la sugerencia de conectar los pines de entrada no usados a alguna seal estable para evitar que por ellos se filtre algn ruido que pueda perturbar la operacin del LCD. LCDs con iluminacin de fondo. Esta caracterstica se basa en diferentes tecnologas, siendo el empleo de una matriz de LEDs colocados detrs de la pantalla el mtodo ms habitual. T sabes que hay todo tipo de LEDs: algunos prenden a penas, mientras que otros, con la misma corriente, pueden servir de faros (bueno, casi :). Creo que eso da cuenta de lo mucho que puede variar el uso de la iluminacin de un modelo a otro. La iluminacin suele activarse con los pines 15 y 16, pero su polaridad tambin vara entre modelos. Sobra decir, por tanto, que sera mejor que chequees el datasheet de tu LCD si es uno de estos.

Como sea, los pines que activan la iluminacin suelen ser independientes de los 14 estndares y las prcticas de este curso deberan funcionar con iluminacin o sin ella. Control del Display LCD Tenemos a continuacin una librera para controlar un LCD con una interface de 4 bits y usando el bit BF (Busy Flag). Si tuviste la paciencia de leer las pginas anteriores, vers que es un claro reflejo de todo lo expuesto. Si no, al menos debes concentrarte en los encabezados de las funciones (y sus comentarios) para saber cmo utilzarlas. Las libreras en el lenguaje C se suelen dividir en dos archivos: uno (con extensin .c) que contiene los cdigos ejecutables de las funciones y otro (con extensin .h) donde se escriben las definiciones y los prototipos de las funciones, bsicamente. De ese modo, la librera para el LCD que usaremos consta de los archivos lcd.h y lcd.c. Para controlar el LCD debes colocar ambos archivos preferentemente en la misma carpeta del proyecto de trabajo y luego escribir en tu programa la directiva: #include lcd.h Tampoco debes olvidar aadirlos al proyecto en el entorno de BoostC mediante la opcin Add Files to Project... del men Project o de la ventanita Workspace. /////////////////////////////////////////////////////////////////////////////// // File Name: lcd.c // Processor: Todos los PICmicros // Compiler: BoostC // // Purpose: Librera de funciones para controlar un display LCD con chip // Hitachi HD44780 o compatible. La interface es de 4 bits. /////////////////////////////////////////////////////////////////////////////// #include lcd.h //**************************************************************************** // Ejecuta la inicializacin software completa del LCD. La configuracin es

CURSO_MICROS

P g i n a | 126

// de: interface de 4 bits, despliegue de 2 lneas y caracteres de 5x7 puntos. //**************************************************************************** void lcd_init(void) { lcd_E = 0; // Valores iniciales de E, RW y RS lcd_RS = 0; // lcd_RW = 0; // lcd_Etris = 0; // Direcciones de E, RW y RS lcd_RStris = 0; // lcd_RWtris = 0; // delay_ms(45); // > 40 ms lcd_nibble(0b00110000); // Function Set: 8-bit delay_ms(5); // > 4.1 ms lcd_nibble(0b00110000); // Function Set: 8-bit delay_ms(1); // > 100 s lcd_nibble(0b00110000); // Function Set: 8-bit delay_ms(1); // > 40 s lcd_nibble(0b00100000); // Function Set: 4-bit delay_ms(1); // > 40 s lcd_nibble(0b00100000); // Function Set: 4-bit, 2lines, 47font lcd_nibble(0b10000000); // lcd_write(0b00001100, 0); // Display ON/OFF Control: Display on, Cursor off, Blink off lcd_write(0b00000001, 0); // Clear Display lcd_write(0b00000110, 0); // Entry Mode Set } //**************************************************************************** // Escribe una instruccin en el LCD: // Si RS = 0 la instruccin es de comando (Function Set, Entry Mode set, etc). // Si RS = 1 la instruccin es de dato y va a la DDRAM o CGRAM. //**************************************************************************** void lcd_write(char inst, char RS) { while(lcd_read(0)&0x80) // Mientras LCD siga ocupado continue; // seguir esperando if(RS) lcd_RS = 1; // Para escribir en DDRAM o CGRAM else lcd_RS = 0; // Para escribir en Registro de Comandos delay_us(5); // Permite actualizar Puntero de RAM lcd_nibble(inst); // Enviar nibble alto lcd_nibble(inst<<4); // Enviar nibble bajo } //**************************************************************************** // Enva el nibble alto de nibble al LCD. //**************************************************************************** void lcd_nibble(char nibble) { lcd_RW = 0; // Modo Escritura lcd_DBUStris &= 0x0F; // Nibble alto salida lcd_DBUS = (nibble&0xF0)|(lcd_DBUS&0x0F); // Colocar dato delay_us(2); // tAS, set-up time > 140 ns lcd_E = 1; // Pulso de Enable delay_us(2); // Enable pulse width > 450 ns

CURSO_MICROS

P g i n a | 127

lcd_E = 0; lcd_DBUStris |= 0xF0; // Nibble alto entrada } //**************************************************************************** // Lee un byte de dato del LCD. // Si RS = 1 se lee la locacin de DDRAM o CGRAM direccionada actualmente. // Si RS = 0 se lee el bit de Busy Flag + el Puntero de RAM. //**************************************************************************** char lcd_read(char RS) { char high, low; if(RS) lcd_RS = 1; // Leer de DDRAM o CGRAM else lcd_RS = 0; // Leer Busy Flag + Puntero de RAM lcd_RW = 1; // Modo Lectura lcd_DBUStris |= 0xF0; // Nibble alto entrada delay_us(2); // tAS, set-up time > 140 ns lcd_E = 1; // Habilitar LCD delay_us(2); // Data Delay Time > 1320 ns high = lcd_DBUS; // Leer nibble alto lcd_E = 0; // Para que LCD prepare el nibble bajo delay_us(2); // Enable cycle time > 1200 ns lcd_E = 1; // Habilitar LCD delay_us(2); // Data Delay Time > 1320 ns low = lcd_DBUS; // Leer nibble bajo lcd_E = 0; return (high&0xF0)|(low>>4); // Juntar nibbles ledos } //**************************************************************************** // Envan cadenas ROM/RAM terminadas en nulo al LCD. //**************************************************************************** void lcd_puts(rom char * s) { unsigned char c, i=0; while(c = s[i++]) lcd_write(c, 1); // Instruccin Write Data to DDRAM/CGRAM } void lcd_puts(char * s) { unsigned char c, i=0; while(c = s[i++]) lcd_write(c, 1); // Instruccin Write Data to DDRAM/CGRAM } //**************************************************************************** // Ubica el cursor del LCD en la columna c de la lnea r. //**************************************************************************** void lcd_gotorc(char r, char c) { if(r==1) r = LCD_LINE1; else r = LCD_LINE2; lcd_write(r+c-1, 0); // Instruccin Set DDRAM Address

CURSO_MICROS

P g i n a | 128

} //**************************************************************************** // Limpia la pantalla del LCD y regresa el cursor a la primera posicin // de la lnea 1. //**************************************************************************** void lcd_clear(void) { lcd_write(LCD_CLEAR, 0); // Instruccin Clear Display } //**************************************************************************** // Envan instrucciones de comando y de datos al LCD. //**************************************************************************** void lcd_cmd(char com) { lcd_write(com, 0); // Cualquier instruccin de comando } void lcd_data(char dat) { lcd_write(dat, 1); // Instruccin Write Data to DDRAM/CGRAM } /////////////////////////////////////////////////////////////////////////////// // File Name: lcd.h // Processor: Todos los PICmicros // Compiler: BoostC // Author: Shawn Johnson /////////////////////////////////////////////////////////////////////////////// #include <system.h> //**************************************************************************** // Configuracin de los pines de interface //**************************************************************************** #define lcd_DBUS portb // Data Bus (RB4-DB4,...,RB7-DB7) #define lcd_DBUStris trisb // #define lcd_E #define lcd_Etris portb.3 // Pin Enable trisb.3 //

#define lcd_RW portb.2 // Pin Read/Write #define lcd_RWtris trisb.2 // #define lcd_RS portb.1 // Pin Register Select #define lcd_RStris trisb.1 // //**************************************************************************** // Cdigos de comando usuales //**************************************************************************** #define LCD_CLEAR 0x01 // Limpiar Display #define LCD_RETHOM 0x02 // Cursor a inicio de lnea 1 #define LCD_LINE1 0x80 // Lnea 1 posicin 0 #define LCD_LINE2 0xC0 // Lnea 2 posicin 0 #define LCD_DDRAM 0x80 // Direccin 0x00 de DDRAM

CURSO_MICROS

P g i n a | 129

#define #define #define #define #define

LCD_CGRAM 0x40 // Direccin 0x00 de CGRAM LCD_CURSOR 0x0E // Mostrar solo Cursor LCD_BLINK 0x0D // Mostrar solo Blink LCD_CURBLK 0x0F // Mostrar Cursor + Blink LCD_NOCURBLK 0x0C // No mostrar ni Cursor ni Blink

//**************************************************************************** // Prototipos de funciones //**************************************************************************** void lcd_init(void); // Inicializa el LCD void lcd_puts(rom char * s); // Enva una cadena rom al LCD void lcd_puts(char * s); // Enva una cadena ram al LCD void lcd_gotorc(char r, char c); // Cursor a fila r, columna c void lcd_clear(void); // Limpia el LCD y regresa el cursor al inicio void lcd_data(char dat); // Enva una instruccin de dato al LCD void lcd_cmd(char com); // Enva una instruccin de comando al LCD char lcd_read(char RS); // Lee un dato del LCD void lcd_write(char inst, char RS); // Escribe una instruccin en el LCD void lcd_nibble(char nibble); Prctica 1 Hellow World Mostrar un mensaje de Hellow World en el LCD es un programa casi tan trillado como hacer parpadear un LED. El circuito

La inteface entre el LCD y el PIC depende en gran medida de la librera usada. En nuestro caso, en lcd.h se configura por defecto una conexin como la mostrada en este esquema. Luego veremos cmo modificar parte de esta interface.

CURSO_MICROS

P g i n a | 130

El pin Vss (o Vo) del LCD establece el contraste de la pantalla. Muchas veces se prefiere quitar el potencimetro y conectar Vss a tierra para fijar el mximo contraste. En los siguientes circuitos haremos algo parecido. El cdigo fuente El programa se compone de tres archivos: el principal, cuyo listado se muestra en seguida, y lcd.c junto con lcd.h. Nota que la directiva solo seala el segundo. //**************************************************************************** // FileName: HelloWorld.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: LCD - Visualizacin de texto //**************************************************************************** #include <system.h> #include lcd.h // Incluir librera de LCD #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 void main(void) { lcd_init(); // Inicializar LCD while(1) { lcd_gotorc(1,7); // Cursor a fila 1 posicin 7 lcd_puts(Hello); // Escribir Hello lcd_gotorc(2,7); // Cursor a fila 2 posicin 7 lcd_puts(World); // ... delay_s(1); // Pausa de 1 segundo lcd_clear(); // Limpiar pantalla delay_s(1); // ... } } Descripcin del programa Obviamente, la primera funcin a llamar debera ser lcd_init. Tras ejecutarse el LCD debe quedar inicializado, con la pantalla limpia y con el cursor en el primer casillero. El LCD tiene un cursor que, si bien puede mostrarse en pantalla, suele configurarse para que permanezca oculto. Bien, visible o no, el cursor avanza automticamente tras cada letra que se escribe. Por ejemplo, luego de escribir Hello el cursor queda apuntando al casillero 13 de la primera fila. La funcin lcd_gotorc sirve para mover el cursor a la posicin deseada. A las tres funciones vistas en este ejemplo solo bastara sumarle lcd_data, que escribe una sola letra en el LCD, para realizar la gran mayora de las aplicaciones. Aun as, ocasionalmente tambin usaremos lcd_cmd, para escribir comandos alternativamente. Por ejemplo:

CURSO_MICROS

P g i n a | 131

lcd_cmd(LCD_LINE2); // Mover cursor al inicio de lnea 2 lcd_cmd(LCD_CLEAR); // Limpiar pantalla lcd_cmd(LCD_CURBLK); // Mostrar Cursor + Blink lcd_cmd(LCD_CURSOR); // Mostrar solo Cursor lcd_cmd(LCD_CGRAM+16); // Mover Puntero de RAM a direccin 16 de la CGRAM Las constantes LCD_CLEAR y otras ms se hallan definidas en el archivo lcd.h. Por cierto, no deberas tocar lcd.c para nada, salvo que por alguna razn bien pensada quieras modificar el cdigo. El cambio ms frencuente a realizar puede ser la configuracin de los pines de interface entre el LCD y el PICmicro. Eso se hace en el archivo lcd.h, en la seccin indicada. //**************************************************************************** // Configuracin de los pines de interface //**************************************************************************** #define lcd_DBUS portb // Data Bus (RB4-DB4,...,RB7-DB7) #define lcd_DBUStris trisb // #define lcd_E #define lcd_Etris portb.3 // Pin Enable trisb.3 //

#define lcd_RW portb.2 // Pin Read/Write #define lcd_RWtris trisb.2 // #define lcd_RS portb.1 // Pin Register Select #define lcd_RStris trisb.1 // Aqu tenemos cuatro pares de directivas. El primero establece el puerto del PICmicro que dar con el bus de datos del LCD. Como solo se toma el nibble alto, en el PIC16F84A no hay otra alternativa que el puerto B. El puerto A no tiene nibble alto completo, tendramos que editar el archivo lcd.c. Los tres pares restantes definen la conexin de las lneas E, RS y RW del LCD. Esto es pin a pin, as que se pueden tomar cualesquiera pines libres del PIC. (RA4 necesitara de una resistencia de pull-up.) Prctica 2 Visualizacin de nmeros Los LCDs solo entienden de caracteres alfanumricos y algunos otros, pero no saben reconocer nmeros. En esta prctica veremos cmo hacerlo. El circuito De ahora en adelante, en vez del potencimetro, colocaremos un diodo 1N4148 en el pin Vee para fijar la tensin (Vdd-Vee) a cerca de 4.3 V. En la mayora de los LCDs este valor brinda un muy aceptable nivel de contraste de la pantalla.

CURSO_MICROS

P g i n a | 132

El cdigo fuente //**************************************************************************** // File Name: lcdNum.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: LCD - Visualizacin de nmeros //**************************************************************************** #include <system.h> #include <stdlib.h> // Contiene la funcin itoa,... #include lcd.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 void main(void) { char buff[10]; // Array de 10 elementos tipo char int i = 0; // Variable i inicializada a 0 lcd_init(); // Inicializar LCD lcd_puts(cursomicros.com); for(;;) { lcd_gotorc(2,4); // Cursor a fila 2 posicin 2 itoa(i, buff, 10); // Convertir i en cadena texto lcd_puts(buff); // Enviar buffer a LCD lcd_gotorc(2,10); // Cursor a fila 2 posicin 10 itoa(i, buff, 16); // Convertir i en texto, base 16 lcd_puts(buff); // Enviar buffer a LCD

CURSO_MICROS

P g i n a | 133

i++; delay_ms(250); delay_ms(250); } }

// Incrementar i // Pausa

Descripcin del programa La funcin lcd_puts recibe como parmetro un array de tipo char, que en su forma ms usada sera una cadena texto. Puede que te hayas fijado en la existencia de dos funciones lcd_puts en la librera del LCD. sa es una caracterstica conocida como sobrecarga de funciones o polimorfismo. El compilador sabe a cul de las dos llamar basndose en el tipo de parmetro que se les enve. Por ejemplo, en el primer caso, lcd_puts(cursomicros.com), llamar a la lcd_puts que recibe como parmetro una cadena constante, porque as he diseado la librera. Por otro lado, no siendo buff un array constante, en el caso de lcd_puts(buff) se llama a la lcd_puts que recibe arrays en ram. Como sea, esas decisiones las tomar el compilador y nosotros nos dejamos de preocupar. Para visualizar nmeros en el LCD primero debemos convertirlos en cadenas de texto. La funcin itoa (acrnimo de Integer TO Ascii) puede hacerlo. Es una funcin incorporada en el C estndar y que tambin la implementan muchos compiladores para microcontroladores, aunque con variaciones en los parmetros que recibe. Podemos hallar itoa y otras funciones anlogas en la librera estndar sdtlib.h. En muchos casos solo se mostrarn los encabezados de las funciones (porque suelen estar precompiladas o implementadas a nivel ensamblador). El siguiente es el encabezado de itoa en BoostC: char* itoa( int val, char* buffer, unsigned char radix ); Es fcil deducir que el primer parmetro es el nmero a convertir; el segundo es el buffer donde nos devolver la cadena de texto y el tercero es la base del nmero. En el programa hemos puesto 10 y 16 en el tercer parmetro porque queremos visualizar el nmero en formato decimal y luego en hexadecimal. (Tambin podra ser, binario u otro) sdtlib.h de BoostC tambin contiene funciones como atoi (que realiza la tarea inversa a itoa), entre otras, pero, sigue siendo bastante limitada comparada con las sdtlib.h de algunos otros compiladores. Prctica 3 Caracteres grficos en LCD La creacin de caracteres grficos puede ser un tema superfluo. Aun as, suponiendo que no faltarn algunas personas obsesivas como yo, que siempre quieren saberlo todo, he preparado esta prctica para cerrar el captulo. Hagamos un poco de memoria. Cuando enviamos el cdigo de un carcter alfanumrico a la DDRAM del LCD, su chip interno buscar en la CGROM el patrn correspondiente y luego lo visualizar en la pantalla. As se escriben todos textos (y as hemos trabajado hasta ahora).

CURSO_MICROS

P g i n a | 134

Ahora bien, si el cdigo enviado vale entre 0x00 y 0x07 (o 0x08 y 0x0F), el chip interno buscar su patrn de visualizacin en la CGRAM. Siendo sta una RAM de lectura/escritura, podemos programar en ella los diseos que se nos ocurran.

Mapa de memoria para la creacin de nuevos caracteres. La CGRAM (Character Generator RAM) consta de 64 bytes en los que se pueden escribir los patrones de 8 nuevos caracteres de 57 puntos 4 caracteres de 510 puntos. Aqu veremos el primer caso. Cuando los caracteres son de 57 puntos los 64 bytes se dividen en 8 bloques de 8 bytes cada uno, y cada bloque almacena el patrn de un nuevo carcter. El esquema mostrado arriba indica que: El primer bloque de CGRAM, con direcciones desde 0b00000000 hasta 0b00000111, corresponde al cdigo 0x00 ( 0x80) de la DDRAM. El segundo bloque CGRAM, con direcciones desde 0b00001000 hasta 0b00001111, corresponde al cdigo 0x01 ( 0x88) de la DDRAM; y as sucesivamente.

CURSO_MICROS

P g i n a | 135

Por ejemplo, la figura de arriba indica que se han rellenado los dos primeros bloques con los patrones de dos pacmans. Hasta ah solo se han creado dos nuevos caracteres. Para mostrarlos en el LCD habra que escribir un cdigo as: lcd_data(0x00); lcd_data(0x01); // Visualizar primer pacman // Visualizar segundo pacman

Pasando a la prctica en s: como parte de su funcionalidad el LCD tiene instrucciones para desplazar lo mostrado en la pantalla hacia un lado u otro. Puede parecer interesante, pero sus limitaciones llevan a muchos a realizar esos efectos mediante rutinas software. Pues es lo que haremos en esta prctica, mostrar por el LCD un mensaje que pasa como una marquesina, y como nuevo carcter pondremos a un pacman glotn que en la esquina inferior izquierda. El circuito

El cdigo fuente //**************************************************************************** // FileName: Pacman.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: LCD - Creacin de caracteres grficos personalizados //**************************************************************************** #include <system.h> #include lcd.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON

CURSO_MICROS

P g i n a | 136

#pragma CLOCK_FREQ 4000000 /* Definiciones */ #define LCD_LEN 16 // Para LCD de 216 #define PacOpen 0x00 // Indentificador de carcter nuevo #define PacShut 0x01 // Indentificador de carcter nuevo void main(void) { unsigned char j; // ndice relativo unsigned char i; // ndice base char c; rom char * PattOpen = {0x0F,0x1C,0x18,0x10,0x18,0x1C,0x0F,0x00}; // Pattern rom char * PattShut = {0x00,0x0E,0x1F,0x10,0x1F,0x0E,0x00,0x00}; // Pattern rom char * Taine = \EL HAMBRE PRODUCE POEMAS INMORTALES. \ LA ABUNDANCIA, SOLAMENTE INDIGESTIONES Y TORPEZAS\ ; lcd_init(); /* Crear dos nuevos caracteres (los pacmans) en la CGRAM */ lcd_cmd(LCD_CGRAM); // Instruccin Set CGRAM Address for (i=0; i<8; i++) // Volcar patrn de pacman 1 lcd_data(PattOpen[i]); // lcd_cmd(LCD_CGRAM + 8); // Instruccin Set CGRAM Address for (i=0; i<8; i++) // Volcar patrn de pacman 2 lcd_data(PattShut[i]); // lcd_clear(); // LImpiar pantalla y regresar a DDRAM lcd_puts( Hungry Pacman ); // Escribir Hungry Pacman en LCD while(1) { i = 0; while(1) { lcd_cmd(LCD_LINE2); // Cursor a inicio de lnea 2 if(i&0x01) // Si bit 0 de i es 1, lcd_data(PacOpen); // enviar pacman abierto else // Si no, lcd_data(PacShut); // enviar pacman cerrado for(j=0; j<LCD_LEN-1; j++) { c = Taine[i+j]; // Obtener dato de matriz if // Si es dato vlido, lcd_data; // enviarlo a LCD else // Si no (c = 0x00 = fin), goto exit; // salir de los dos bucles } delay_ms(200); // Pausa delay_ms(200); i++; } exit: // sta es una etiqueta } }

CURSO_MICROS

P g i n a | 137

Descripcin del programa Despus de iniciado el LCD, los datos que se le enven irn a la DDRAM (para mostrar caracteres en la pantalla). Como los patrones de los pacman deben ir en la CGRAM necesitamos establecerla como destino. Para eso enviamos el comando Set CGRAM Address con la direccin de CGRAM que queremos acceder. La otra sentencia lcd_cmd(LCD_CGRAM+8) permitir que los siguientes datos vayan al segundo bloque (de 8 bytes) de la CGRAM. Nota que no era necesario porque el Puntero de RAM ya estaba apuntando a esta direccin. Como hemos creado los dos pacmans en los dos primeros bloques (de 8 bytes) de la CGRAM, los cdigos para accederlos sern 0 (PacOpen) y 1 (PacShut), repectivamente. A continuacin se encuentra la sentencia lcd_clear. Con ella no solo limpiamos la pantalla del LCD (que, por cierto, ya estaba limpia) sino que volvemos a cambiar a la DDRAM. Por si no qued claro cmo se forman los patrones de los dos pacmans, aqu los tenemos solitos. (Los bits no importan, pueden ser 1s 0s.)

rom char * PattOpen = {0x0F,0x1C,0x18,0x10,0x18,0x1C,0x0F,0x00}; //Pattern rom char * PattShut = {0x00,0x0E,0x1F,0x10,0x1F,0x0E,0x00,0x00}; //Pattern Pasando a otros temas: el texto de la pantalla se desplaza una posicin cada 400 ms. Si te parece que avanza muy lento, puedes disminuir esta pausa. No obstante, podras empezar a ver como si hubiera dos letras por casillero de la pantalla. Ello se debe a que el carcter enviado al LCD no se muestra ni se borra de inmediato. Es lo que sus datasheets llaman tiempo de respuesta de visualizacin. Sabemos que una cadena de texto se delimita entre com illas ( ). As que luego nos surge la pregunda: cmo hacemos si queremos incluir comillas dentro de la cadena? Las ponemos al lado del signo \. Con eso el par \ ser entendido por el compilador como . Son las populares secuencias de escape. rom char * Taine = \EL HAMBRE PRODUCE POEMAS INMORTALES. \ LA ABUNDANCIA, SOLAMENTE INDIGESTIONES Y TORPEZAS\ ; En general, a diferencia del Basic, en C es muy mal visto el uso de un goto, salvo un caso extremo. goto funciona como en el ensamblador: salta a otro punto del programa, identificado con una etiqueta. Mi goto salta a la etiqueta GetOut para salir de dos bucles al mismo tiempo. Dicen que se es uno de los pocos casos considerados extremos: salir

CURSO_MICROS

P g i n a | 138

intempestivamente de varios bucles anidados. A decir verdad, siempre hay algoritmos alternativos para evitar el goto. El teclado matricial Operacin del teclado matricial Un teclado matricial es un conjunto de botones (swithes) dispuestos en forma de malla, de manera que no se requieran de muchas lneas para su interface. De hecho, la mayora de los teclados (incluyendo quiz el de tu ordenador) funciona con una estructura similar. Bueno, no nos adelantemos tanto; en este captulo trabajaremos con un teclado de 44. Como se aprecia en la siguiente imagen, cada botn del teclado est conectado a alguna de las filas Row, por un lado; y por el otro, a alguna de las columnas Col.

Aspecto fsico y estructura interna de un teclado. La siguiente figura esboza la conexin entre un C y un teclado de 44. Obviamente, no se puede leer el estado de una tecla como un pulsador cualquiera. Pero es fcil darse cuenta de que una tecla pulsada establece la conexin entre una de las filas Row y una de las columnas Col.

Conexin de un teclado a un microcontrolador.

CURSO_MICROS

P g i n a | 139

Por ejemplo, al presionar la tecla 6 se unen las lneas Row 1 y Col 2. O sea, si sacamos un 1 ( 0) por el pin de Row 1, tambin deberamos leer un 1 ( 0) en el pin de Col 2, o viceversa. Generalizando, solo hay un par Row-Col que identifica cada tecla. En consecuencia, para saber cul fue la tecla pulsada debemos sondear una a una todas las combinaciones Row-Col. Una vez detectada la condicin de circuito cerrado, se usa el par Row-Col para deducir la posicin de la tecla pulsada. Control de un teclado matricial Apuesto a que luego de expuesta la relativa sencillez de este teclado debes estar ansioso por empezar a ensayar tu propio cdigo para controlarlo. Bueno, a mi me pas la primera vez y, a decir verdad, no fue nada, considerando que practicaba con un PIC16F877A (al que le sobran puertos). Mi dolor de cabeza era la necesidad de multiplexar las lneas de interface del teclado con las del LCD en un PIC16F84A. Un mnimo decuido causara un cortocircuito que daara el PIC o el LCD, o ambos. Pero al final fue igual de fcil. Ni siquiera hicieron falta circuitos de aislamiento a base de diodos ni resistencias de pull-up externas. As que puedes asumirlo como reto y ver cmo te va a ti. Si no te interesa y, como con el LCD, solo quieres una librera de subrutinas para ver resultados directos, quiz te interese la ma. Esta compuesta por los archivos keypad.h y keypad.c. (Cosa inesperada :-). En el interior de estas funciones el nibble bajo de PORTB se configura como salida y el nibble alto, como entrada. Al salir de las mismas todo PORTB queda como entrada para facilitar su posible posterior uso para otras funciones. Segn mi cdigo, el valor ledo en las columnas cuando no hay teclas pulsadas debera ser 1 lgico, y 0 cuando si las hay. Para eso es necesario que dichas lneas estn sujetas a VDD por medio de resistencias de pull-up. Y ya que el puerto B del PIC tiene pull-ups internas que actan en los pines de entrada... Todo encaja genial. Como siempre, debemos copiar los dos archivos en la carpeta del proyecto, luego utilizar la opcin Add Files to Project del men Project o de la ventanita Workspace de BoostC. En el programa principal solo es necesario incluir keypad.h con la directiva: #include keypad.h /////////////////////////////////////////////////////////////////////////////// // File Name: keypad.c // Processor: Todos los PICmicros // Compiler: BoostC // Purpose: Librera para controlar un teclado de 44 /////////////////////////////////////////////////////////////////////////////// #include keypad.h //**************************************************************************** // Escanea el teclado y retorna el valor ASCII de la tecla presionada por // al menos 25ms. En otro caso retorna 0x00. //**************************************************************************** char keypad_read(void)

CURSO_MICROS

P g i n a | 140

{ char c1, c2; c1 = keypad_scan(); // Escanear teclado if(c1) // Si hubo alguna tecla pulsada { delay_ms(25); // Delay antirrebote c2 = keypad_scan(); // Escanear otra vez if( c1==c2 ) // Si Ambas teclas ledas son iguales return c2; // entonces aceptarla } return 0x00; } //**************************************************************************** // Espera hasta que el teclado quede libre. //**************************************************************************** void keypad_released(void) { delay_us(10); // while(keypad_scan()) // Mientras se detecte alguna tecla pulsada continue; // seguir escaneando. } //**************************************************************************** // Escanea el teclado y retorna el valor ASCII de la primera tecla que // encuentre pulsada. De otro modo retorna 0x00. //**************************************************************************** char keypad_scan(void) { unsigned char Col, Row; char RowMask, ColMask; // Col0 Col1 Col2 Col3 rom char * keys = {7, 8, 9, A, // Row 0 4, 5, 6, B, // Row 1 1, 2, 3, C, // Row 2 ., 0, #, D}; // Row 3 kpd_PORTtris = 0xF0; // Nibble alto entrada, nibble bajo salida RowMask = 0b11111110; // Inicializar RowMask for(Row=0; Row<4; Row++) { kpd_PORT = RowMask; // delay_us(10); // Para que se estabilice la seal ColMask = 0b00010000; // Inicializar ColMask for(Col=0; Col<4; Col++) { if((kpd_PORT&ColMask)==0) // Si hubo tecla pulsada { kpd_PORTtris = 0xFF; // Todo puerto entrada otra vez return keys[4*Row+Col]; // Retornar tecla pulsada }

CURSO_MICROS

P g i n a | 141

ColMask <<= 1; // Desplazar ColMask para escanear } // siguiente columna RowMask <<= 1; RowMask |= 0x01; } // Se llega aqu si no se hall ninguna tecla pulsada kpd_PORTtris = 0xFF; // Todo puerto entrada otra vez return 0x00; // Retornar Cdigo de no tecla pulsada } /////////////////////////////////////////////////////////////////////////////// // File Name: keypad.h // Processor: Todos los PICmicros // Compiler: BoostC // Author: Shawn Johnson // Purpose: Librera para controlar un teclado de 44 /////////////////////////////////////////////////////////////////////////////// #include <system.h> //**************************************************************************** // Configuracin del puerto de interface //**************************************************************************** #define kpd_PORT portb // Puerto #define kpd_PORTtris trisb // Direccin de puerto //**************************************************************************** // Prototipos de funciones //**************************************************************************** char keypad_read(void); // Retorna el valor ASCII de la tecla presionada // o retorna 0x00 si no se presion nada void keypad_released(void); // Espera hasta que el teclado est libre char keypad_scan(void); // Escanea el teclado Programacion del teclado matricial Prctica 1 Lectura de teclado mediante sondeo Segn las teclas pulsadas, se llevan a cabo las siguientes instrucciones del LCD: A: coloca el cursor al inicio de la pantalla del LCD. B: avanza el cursor una posicin adelante, a la derecha en este caso. C: retroceder el cursor una posicin atrs. D: limpia la pantalla del LCD. // Desplazar RowMask para escanear // siguiente fila

Cualquier otra tecla ser visualizada en la pantalla del LCD.

CURSO_MICROS

P g i n a | 142

El circuito Mira esa maraa de cables. Todo controlado por un PIC16F84A; y an sobra un pin libre. No est de ms recordarte que debes tener mucho cuidado al armarlo. Una mala conexin podra dar en un cortocircuito de consecuencias fatales. A proposito, la librera del LCD debe ser modificada para que sus pines de control (E, RW y RS) se conecten al puerto A.

El cdigo fuente El programa consta de varios archivos. El siguiente listado es el archivo principal. Las libreras del teclado y LCD tambin deberan estar presentes en la carpeta del proyecto y aadidas debidamente en el entorno del compilador. //**************************************************************************** // File Name: KeypadPoll.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Control de teclado mediante sondeo //**************************************************************************** #include <system.h> #include lcd.h // Incluir librera de LCD #include keypad.h // Incluir librera de Teclado

CURSO_MICROS

P g i n a | 143

#pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 void main(void) { char c; lcd_init(); // Inicializar LCD lcd_cmd(LCD_CURBLK); // Comando para mostrar Cursor + Blink option_reg.NOT_RBPU = 0; // Habilitar weak pull-ups (para teclado) while(1) { nop(); // Alguna otra tarea c = keypad_read(); // Leer teclado if // S hubo Tecla pulsada (si es diferente de 0) { switch { case A: lcd_cmd(LCD_RETHOM); break; // Cursor al inicio case B: lcd_cmd(0x14); break; // Cursor a la derecha case C: lcd_cmd(0x10); break; // Cursor a la izquierda case D: lcd_cmd(LCD_CLEAR); break; // Limpiar pantalla default: lcd_data; break; // Mostrar tecla } keypad_released(); // Esperar teclado libre } } } Advertencia! Es evidente que la conexin del LCD ha variado respecto de los circuitos anteriores. Por tanto, el archivo lcd.h tambin debera ser editado de manera que las lneas E, RW y RS del LCD coincidan con los pines RA2, RA1 y RA0, tal como se muestra en El circuito. ////////////////////////////////////////////////////////////////////////////// // FileName: lcd.h ... ... //**************************************************************************** // Configuracin de los pines de interface //**************************************************************************** #define lcd_DBUS portb // Data Bus (RB4-DB4,...,RB7-DB7) #define lcd_DBUStris trisb // #define lcd_E #define lcd_Etris porta.2 // Pin Enable trisa.2 //

#define lcd_RW porta.1 // Pin Read/Write #define lcd_RWtris trisa.1 // #define lcd_RS porta.0 // Pin Register Select #define lcd_RStris trisa.0 //

CURSO_MICROS

P g i n a | 144

Descripcin del cdigo Los cdigos de comando LCD_RETHOM y LCD_CLEAR se hallan en el archivo lcd.h, ya que son algo ms usuales que 0x14 y 0x10. Estos los saqu directamente de la descripcin de la instruccin Cursor or Display Shift (Desplazamiento de Cursor o Display) del captulo anterior. Por lo dems, creo que sera mejor examinar el funcionamiento de todo el programa en un simulador como Proteus VSM (para lo cual te sugiero poner switches en lugar de algunas teclas). Como de costumbre, puedes hallar todos los archivos preparados en la web. Por cierto, las versiones pasadas de Proteus no simulan bien las pull-ups internas de los Cs. De tener una de sas, no deberan sorprenderte los mensajes de contention on net en la ventana Simulation Log. Prctica 2 Control de teclado mediante interrupciones Esta prctica corresponde ms al tema del siguiente captulo. As que si no te importa rearmar el circuito, puedes dejarla para ms adelante. Alguna vez le en uno de los documentos de Microchip que la interrupcin de Cambio de PORTB fue pensada para controlar teclados matriciales como el que estamos utilizando; y parece ser muy cierto por la forma como se realiza el acoplamiento. La parte visible de esta prctica es similar al de la anterior. La diferencia es que ahora el PIC permanece en modo Sleep y despierta solo cuando se pulsa una tecla para leerla y mostrarla en el LCD. El circuito es el mismo de la prctica anterior y no volver a mostrarlo.

CURSO_MICROS

P g i n a | 145

El cdigo fuente El siguiente es el archivo principal. Aparte de l, no olvides editar la librera del LCD para que concuerde con el circuito de la prctica. //**************************************************************************** // File Name: KeypadInt.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Control de Teclado mediante Interrupciones //**************************************************************************** #include <system.h> #include lcd.h #include keypad.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 void SetupInt(void); // Prototipo de funcin

//**************************************************************************** // Manejador de interrupcin o Rutina de servicio de interrupcin (ISR) // Se da por hecho que se trata de la Interrupcin de Cambio de PORTB. //**************************************************************************** void interrupt(void)

CURSO_MICROS

P g i n a | 146

{ char c = keypad_read(); // Leer teclado if // Si fue tecla vlida { switch { case A: lcd_cmd(LCD_RETHOM); break; // Cursor al inicio case B: lcd_cmd(0x14); break; // Cursor a la derecha case C: lcd_cmd(0x10); break; // Cursor a la izquierda case D: lcd_cmd(LCD_CLEAR); break; // Limpiar pantalla default: lcd_data; break; // Mostrar tecla } keypad_released(); // Esperar teclado libre } SetupInt(); // Preparar para siguiente int. intcon.RBIF = 0; // Limpiar flag } //**************************************************************************** // Funcin principal //**************************************************************************** void main(void) { lcd_init(); lcd_cmd(LCD_CURBLK); // Comando para mostrar Cursor + Blink option_reg.NOT_RBPU=0; // Habilitar weak pull-ups (para el teclado) SetupInt(); // Preparar PORTB para recibir ints. intcon = (1<<RBIE)|(1<<GIE); // Habilitar interrupcin de Cambio de PORTB for(;;) { sleep(); // Entrar en modo Standby nop(); // ... } } //**************************************************************************** // Prepara el puerto B para que detecte un cambio de tensin cuando // se presione una tecla. Al mismo tiempo se limpia la condicin de // interrupcin de Cambio de PORTB. //**************************************************************************** void SetupInt(void) { trisb &= 0xF0; // Nibble de Rows salida portb &= 0xF0; // Nibble de Rows bajo } Descripcin del programa Quien nunca vio un programa con interrupciones puede que se est rascando la cabeza preguntndose por dnde empieza o termina la ejecucin de este cdigo. Si un programa siempre empieza en funcin main, cmo es que se lee el teclado si no hay ningn vinculo entre main y la funcin interrupt?

CURSO_MICROS

P g i n a | 147

Entre otras cosas las interrupciones brindan una potente forma de atender un evento, en este caso la pulsada de un botn del teclado sin tener que estar revisndolo todo el tiempo como en la prctica anterior. Observa que en el bucle infinito de main el PIC se queda en Standby, o sea, no hace nada, como durmiendo. El sleep() en este programa no es indispensable, as que lo podras quitar imaginariamente. Igual el programa se quedara haciendo nop, nada. for(;;) { sleep(); nop(); }

// Entrar en modo Standby // ...

Solo cuando se presione alguna tecla el PIC (despertar y) pasar de inmediato a ejecutar la funcin interrupt, donde s hay cdigo para leer el teclado. interrupt es una funcin especial que no necesita ser llamada por software como las funciones ordinarias; se llama va hardware. Impresionado? Espero al menos haber despertado tu inters por el siguiente captulo. All las prcticas son bastante simples para asimilar bien el concepto de las interrupciones y su manejo. Creo que as se endenderan mejor cosas como las explicadas en el siguiente prrafo. Segn el circuito y la librera del teclado, cuando no hay teclas pulsadas las lneas Col (nibble alto de PORTB) se leen como 1 lgico (gracias a las pull-ups), y as deberan permancer mientras el PIC est soando. Por tanto, para que haya un cambio de nivel al pulsar una tecla, las lneas de Row (nibble bajo de PORTB) deberan sacar 0 lgico. De esto se encarga la funcin SetupInt. //**************************************************************************** // Prepara el puerto B para que detecte un cambio de tensin cuando // se presione una tecla. Al mismo tiempo se limpia la condicin de // interrupcin de Cambio de PORTB. //**************************************************************************** void SetupInt(void) { trisb &= 0xF0; // Nibble de Rows salida portb &= 0xF0; // Nibble de Rows bajo }

Interrupciones Interrupciones en los PIC16F84A y PIC16F87XA Introduccin Hay una analoga que siempre recuerdo desde que la le en un buen libro de Turbo Pascal cuando aprenda a programar en dicho lenguaje. Cuando vamos a recibir una visita en nuestra casa podemos ir a la puerta a cada momento para ver si ya lleg y atenderla apropiadamente, o podemos quedarnos haciendo nuestras labores cotidianas esperando a que sea la visita quien llame a la puerta para ir a recibirla.

CURSO_MICROS

P g i n a | 148

Ir a la puerta constantemente se compara por ejemplo con testear los puertos del PIC para ver si se presion algn pulsador o algn teclado y actuar en consecuencia. Eso se conoce como tcnica Polling o de sondeo e implica el desperdicio de recursos y ciclos de CPU. En este captulo aprenderemos a atender nuestras visitas justo cuando llamen a la puerta para que el PIC no se canse en vano y que se ponga a dormir, si fuera posible. sta es solo una pequea muestra de lo que se puede conseguir con las interrupciones. Qu son las Interrupciones? Una interrupcin es una llamada inesperada, urgente e inmediata a una funcin especial denominada Interrupt Service Routine (ISR) y que en BoostC se llama interrupt. El mecanismo funciona as: sin importar lo que est haciendo en main o cualquier funcin relacionada con main, cuando ocurra la interrupcin el CPU har una pausa y pasar a ejecutar la funcin interrupt. Al terminarla el CPU regresar al cdigo que estaba ejecutando antes de la interrupcin, justo donde lo haba suspendido. Fuentes de interrupcin Aunque es posible provocar interrupciones desde el programa llamndolas como si fueran funciones ordinarias, las interrupciones son disparadas (llamadas) por eventos del hardware del microcontrolador. El evento puede ser algn cambio en cierto pin de E/S, el desbordamiento de un Timer, la llegada de un dato serial, etc. Se puede deducir por tanto que las fuentes de interrupcin estn relacionadas con la cantidad de recursos del microcontrolador. La tabla mostrada ms abajo presenta las 15 interrupciones posibles de los PIC16F87xA. Respecto al PIC16F84A, debes saber que l solo cuenta con las 4 primeras interrupciones citadas. Hay una pequea diferencia con la interrupcin de la EEPROM interna, pero como no se suele usar (al menos en este curso), el tratamiento de las dems interrupciones debera ser vlido y aplicable tanto si trabajamos con el PIC16F84A como con los PIC16F87xA, siempre que est disponible, claro est. En este captulo estudiaremos a fondo las dos primeras interrupciones. La interrupcin del Timer0 y las dems sern discutidas en sus mdulos respectivos.

CURSO_MICROS

P g i n a | 149

Interrupcin Enable bits Interrupcin RB0/INT INTE Interrupcin de Cambio de PORTB RBIE GIE Interrupcin del TMR0 T0IE Interrupcin de la EEPROM interna EEIE Interrupcin del TMR1 TMR1IE Interrupcin del TMR2 TMR2IE Interrupcin del USART - Transmisin TXIE Interrupcin del USART - Recepcin RCIE GIE Interrupcin del mdulo MSSP (I2C o SPI) SSPIE y Interrupcin del mdulo ADC ADIE PEIE Interrupcin del mdulo CCP1 CCP1IE Interrupcin del mdulo CCP2 CCP2IE Interrupcin del mdulo Comparador Analgico CMIE Interrupcin de colisin de bus (en mdulo I2C) BCLIE Interrupcin del Puerto Paralelo Esclavo PSPIE

Flag bits INTF RBIF T0IF EEIF TMR1IF TMR2IF TXIF RCIF SSPIF ADIF CCP1IF CCP2IF CMIF BCLIF PSPIF

Constrol de Interrupciones en los PIC16F84A y PIC16F87XA Control de interrupciones Hay dos tipos de bits para controlar las interrupciones: los Bits Enable, que habilitan las interrupciones, y los Bits de Flag, que indican cul interrupcin se ha producido. Bueno, eso para decirlo a grandes rasgos. Puedes apreciarlos en la tabla mostrada en la seccin anterior. Hay un bit enable para cada interrupcin y adems hay dos bits enable generales llamados GIE (Global Interrupt Enable) y PEIE (Peripheral Interrupt Enable). En todo microcontrolador hay un bit como GIE, pero el bit PEIE est de sobra en los PICs. La ltima parte fue mi opinin personal, por si acaso ;) Para habilitar una interrupcin hay que setear los bits indicados en la tabla. Por ejemplo, si queremos habilitar la interrupcin RB0/INT, debemos setear los bits INTE y GIE. Si queremos habilitar la interrupcin del conversor ADC, debemos setear los bits ADIE, PEIE y GIE. Por otro lado, cada interrupcin tiene un Bit de Flag nico, que se setea automticamente por hardware cuando ocurre el evento de dicha interrupcin. Eso pasar independientemente de si la interrupcin est habilitada o no. Si la interrupcin fue previamente habilitada, se disparar. Cualquiera que sea la interrupcin disparada, se llamar a la misma funcin interrupt del programa. Es aqu donde entran a jugar los bits de flag. Testeando cul de ellos se ha activado se podr averiguar la fuente de la interrupcin. Algo importante: los bits de flag se activan por hardware, pero en general, somos nosotros quienes debemos limpiarlos por software en la funcin interrupt. Si no lo hiciramos, se volvera a disparar la misma interrupcin una y otra vez. En otras palabras, el programa se colgara en la funcin interrupt.

CURSO_MICROS

P g i n a | 150

Todos los bits de control de interrupciones se encuentran en los siguientes registros: INTCON (Interrupt Control). Contiene bits enable y bits de flag. PIE1 (Peripheral Interrupt Enable 1). Contiene bits enable. PIE2 (Peripheral Interrupt Enable 2). Contiene bits enable. PIR1 (Peripheral Interrupt Request 1). Contiene bits de flag. PIR2 (Peripheral Interrupt Request 2). Contiene bits de flag. PEIE T0IE INTE RBIE T0IF INTF RBIF

INTCON GIE PIE1 PIR1 PIE2 PIR2

PSPIE ADIE RCIE TXIE SSPIE CCP1IE TMR2IE TMR1IE PSPIF ADIF RCIF TXIF SSPIF CCP1IF TMR2IF TMR1IF ----CMIE --CMIF --EEIE BCLIE --EEIF BCLIF ------CCP2IE CCP2IF

Creo que cuando llegues a dominar el manejo de todos estos registros podrs reclamar tu cinturn negro en programacin del PIC16F87xA ;) Pero hablando en serio, aunque eso no sera nada del otro mundo, no todos los bits se usan con la misma necesidad. Por ejemplo, los registros PIE2 y PIR2 no se tocaran de no ser quizs por los bits CCP2IE y CCP2IF. De momento te pedir que no pierdas de vista al registro INTCON. All estn los bits de las interrupciones que estudiaremos en ste y en el siguiente captulo. El Registro INTCON GIE PEIE Bit 7 GIE: Global Interrupt Enable bit 1 = Habilita todas las interrupciones no enmascaradas T0IE INTE RBIE T0IF INTF RBIF Bit 0

0 = Inhabilita todas las interrupciones PEIE: Peripheral Interrupt Enable bit 1 = Habilita las interrupciones perifricas no enmascaradas 0 = Inhabilita las interrupciones perifricas T0IE: TMR0 Overflow Interrupt Enable bit 1 = Habilita interrupcin del TMR0 0 = Inhabilita interrupcin del TMR0 En los PIC16F87xA este bit se llama tambin TMR0IE INTE: RB0/INT External Interrupt Enable bit 1 = Habilita interrupcin externa RB0/INT 0 = Inhabilita interrupcin externa RB0/INT RBIE: RB Port Change Interrupt Enable bit

CURSO_MICROS

P g i n a | 151

GIE PEIE T0IE INTE RBIE T0IF 1 = Habilita interrupcin de Cambio de puerto B

INTF

RBIF

0 = Inhabilita interrupcin de Cambio de puerto B T0IF: TMR0 Overflow Interrupt Flag bit 1 = El registro TMR0 se desbord (debe limpiarse en software) 0 = El registro TMR0 no se desbord En los PIC16F87xA este bit se llama tambin TMR0IF INTF: RB0/INT External Interrupt Flag bit 1 = Ocurri la interrupcin externa RB0/INT (debe limpiarse en software) 0 = No ocurrio la interrupcin externa RB0/INT RBIF: RB Port Change Interrupt Flag bit 1 = Al menos uno de los pines RB7:RB4 ha cambiado su estado 0 = Ninguno de los pines RB7:RB4 ha cambiado su estado Control de Interrupcin RB0/INT El evento que puede disparar esta interrupcin es un flanco de subida o de bajada detectado en el pin RB0/INT. INTCON GIE PEIE T0IE INTE RBIE T0IF INTF RBIF

Para habilitar esta interrupcin hay que setear los bits INTE y GIE. Adems es condicin necesaria que el pin RB0 est configurado como entrada. Una vez producido el evento, el hardware setear el flag INTF, y luego se disparar la interrupcin si est habilitada. INTF se debe limpiar por software. Lo que an falta configurar es el flanco de accin. Para ello se usa el bit INTEDG, del registro OPTION_REG. Si INTEDG = 0, la interrupcin se dar en el flanco de bajada. Si INTEDG = 1, la interrupcin se dar en el flanco de subida (valor por defecto).

La interrupcin externa RB0/INT tiene la capacidad de despertar al PIC, es decir, de sacarlo del modo sleep o standby. sta es una caracterstica muy notable, que veremos luego. El registro OPTION_REG Los seis primeros bits de OPTION_REG sirven para configurar las funciones del Timer0; son los que en la siguiente imagen aparecen sombreados y que de momento no sern detallados. Aunque el bit /RBPU tampoco interviene directamente con la Int RB0/INT, las resistencias de weak pull-up pueden ser aprovechables ya que el pin RB0 ser programado como entrada.

CURSO_MICROS

P g i n a | 152

Dos observaciones: primero, no debemos confundir este registro con OPTION, que era una vieja directiva del ensamblador y que actualmente ha cado en la lista de los denominados deprecated, o sea, obsoletos y prohibidos. Y segundo: debes saber que OPTION_REG es el nico registro (aparte de los TRISx) que tras un reset inicia con todos sus bits a 1. RBPU INTEDG Bit 7 T0CS T0SE PSA PS2 PS1 PS0 Bit 0 RBPU: PORTB Pull-up Enable bit 1 = Inhabilitar Pull-ups de PORTB 0 = Habilitar Pull-ups de PORTB en los pines configurados como entradas En los archivos de dispositivo de Microchip este bit se llama NOT_RBPU.

INTEDG:

Interrupt Edge Select bit

1 = Interrupcin en flanco de subida del pin RB0/INT 0 = Interrupcin en flanco de bajada del pin RB0/INT Uso de Interrupcin RB0/INT Prctica 1 Interrupcin Externa RB0/INT En estas prcticas de ejemplo evitaremos programas sofisticados con cdigos grandes que desven la atencin hacia una breve aplicacin de la teora expuesta. Por eso no nos vendr mal volver a los socorridos LEDs parpadeantes o basculantes. El programa tendr dos tareas: la rutina principal se encargar de parpadear un LED y la funcin de interrupcin har bascular otro LED cada vez que presionemos un pulsador. Esto ser como fusionar dos programas que alguna vez hicimos. Correr dos programas a la vez... Dicen que algo as le paso por la cabeza a Bill Gates cuando pens en MS Windows. De los dos flancos que se generan al presionar el botn escogeremos el flanco de bajada para disparar la interrupcin RB0/INT.

CURSO_MICROS

P g i n a | 153

El circuito

El cdigo fuente

//**************************************************************************** // FileName: BlinkTogLed.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Uso de la interrupcin RB0/INT //**************************************************************************** #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 //**************************************************************************** // Interrupt Service Routine, ISR // Se asume que sta es una interrupcion RB0/INT (es la nica habilitada) //**************************************************************************** void interrupt(void) { toggle_bit(porta, 0); // Bascular pin RA0 (LED azul) delay_100us(250); // 50 ms para pasar los rebotes delay_100us(250); // intcon.INTF = 0; // Limpiar flag de interrupcin RB0/INT } //**************************************************************************** // Funcin principal //**************************************************************************** void main() { trisb.0 = 1; // Entrada para botn

CURSO_MICROS

P g i n a | 154

trisa.1 = 0; // Salida para LED rojo trisa.0 = 0; // Salida para LED azul option_reg.INTEDG = 0; // Flanco de bajada de interrupcin RB0/INT intcon = 0x00; // Inhabilitar todas interrupciones y limpiar todos flags intcon.INTE = 1; // Habilitar interrupcin RB0/INT intcon.GIE = 1; // Habilitacin general de interrupciones while(1) // Bucle infinito { toggle_bit(porta, 1); // Permutar pin RA1 (LED rojo) delay_ms(250); // Pausa de 500ms delay_ms(250); // } } Descripcin del programa Cada aplicacin puede tener sus propias especificaciones, pero, en general, un buen hbito de programacin es setear GIE cuando ya todo est listo para atender a la interrupcin. Un poco antes, como el registro INTCON inicia con todos sus bits a 0, quiz mi sentencia INTCON = 0x00 no era necesaria; no en este programa. Ahora analicemos las tareas del programa. Notars que, a simple vista, interrupt es totalmente independiente, es decir, no es referenciada desde ningn punto de main. Una vez habilitada, la interrupcin se disparar cuando alguien presione el botn (flanco de bajada). En ese preciso instante (quiz cuando se est ejecutando toggle_bit(porta, 1) o quiz en algn punto dentro de delay_ms(250)) el CPU pasar a ejecutar la funcin interrupt. void interrupt(void) { toggle_bit(porta, 0); // Bascular pin RA0 (LED azul) delay_100us(250); // 50 ms para pasar los rebotes delay_100us(250); // intcon.INTF = 0; // Limpiar flag de interrupcin RB0/INT } Cuando se produjo el evento de esta interrupcin el hardware activ el flag INTF y ahora debe ser limpiado antes de salir de interrupt o al entrar en ella. Eso depender de cada aplicacin. Como sea, de no hacerlo, la interrupcin se volvera a disparar sin motivo una y otra vez. Al salir de interrupt, el CPU regresar a continuar la tarea que estaba ejecutando antes de la interrupcin. Termino comentando que la razn por la que puse un delay de 50 ms usando delay_100us es porque no es recomendable (o no se debe y/o no se puede) llamar desde interrupt una funcin que pueda estar ejecutndose en main. Una funcin con esa caracterstica se llama reentrante.

CURSO_MICROS

P g i n a | 155

El Modo Sleep El modo sleep, power-down o standby es un estado en que se detiene el oscilador del sistema y, por tanto, dejan de funcionar todas las partes del PIC que dependen de l, incluyendo el procesador, es decir, se congela la ejecucin del programa. La pregunta es para qu sirve un PIC con el CPU detenido? Pues hay aplicaciones donde el PIC debe atender ciertas tareas solo cuando ocurre un evento externo como la pulsada de un botn, por ejemplo. El resto del tiempo no hace nada til. Al hacer que el PIC se ponga a dormir y que despierte solo cuando el evento externo se lo demande, se consigue ahorrar muchsima energa que se perdera con el CPU y dems perifricos estando activos en vano. Esto es clave, sobre todo en circuitos alimentados por bateras. Pues bien, el evento por excelencia que despierta al CPU es el disparo de una interrupcin proveniente de una parte del microcontrolador que no dependa del oscilador principal. Entre ellas estn, por ejemplo, la interrupcin de RB0/INT o de Cambio de PORTB. Por otro lado estn los mdulos como el Timer0 o el USART que al operar sincronizados con el oscilador principal, tambin se dormirn en modo Sleep y sus interrupciones no se podrn producir. Para entrar en modo sleep basta con ejecutar la instruccin de ensamblador sleep. Como, al igual que Samara, los procesadores de los ordenadores nunca duermen, en el C estndar no hay una sentencia para la instruccin sleep. Cada compilador para microcontroladores la implementa a su modo. En BoostC y CCS C, por ejemplo, se usa la funcin macro sleep(). Cuando se dispare una interrupcin lo primero que har el CPU al desperar es ejecutar la primera instruccin de ensamblador inmediata a sleep, despus comprobar el estado del bit GIE y, si est seteado, pasar a ejecutar la funcin interrupt. En caso contrario solo seguir su camino. Uso del modo Sleep en los PICs Prctica 2 Durmiendo Si al programa anterior le quitaramos la tarea de la rutina principal, el PIC ya no tendra nada que hacer all. ste puede ser un buen momento para tomar una siesta.

CURSO_MICROS

P g i n a | 156

El circuito

El cdigo fuente

//**************************************************************************** // FileName: Sleeping.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Manejo de interrupcin RB0/INT + modo Sleep //**************************************************************************** #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 //**************************************************************************** // Interrupt Service Routine // Se asume que sta es una interrupcion RB0/INT (es la nica habilitada) //**************************************************************************** void interrupt(void) { toggle_bit(porta, 0); // Bascular pin RA0 (LED azul) delay_ms(50); // Para pasar los rebotes intcon.INTF = 0; // Limpiar flag de interrupcin RB0/INT } //**************************************************************************** // Funcin principal //**************************************************************************** void main() { trisb.0 = 1; // Entrada para RB0/INT

CURSO_MICROS

P g i n a | 157

trisa.0 = 0;

// Salida para LED

option_reg.INTEDG = 0; // Flanco de bajada de interrupcin RB0/INT intcon = (1<<INTE)|(1<<GIE); // Habilitar interrupcin RB0/INT while(1) // Bucle infinito { sleep(); // Entrar en modo Power-Down nop(); } } Descripcin del programa Tras la ejecucin de sleep(); el procesador se detendr, o sea, la ejecucin del programa se congelar en este punto. while(1) { sleep(); nop(); } // Bucle infinito // Entrar en modo Power-Down

El PIC despertar con el disparo de la interrupcin, ejecutar nop()(que tambin equivale a una instruccin de ensamblador), comprobar el bit GIE y, como vale 1, llamar a la funcin interrupt. Lo dems es historia conocida. Ahora pasar a exponer la parte que, me imagino, debe haber hecho rascar la cabeza a los nuevos en C. Bueno, pues sa es mi forma favorita de setear bits en un registro. intcon = (1<<INTE)|(1<<GIE); // Habilitar interrupcin RB0/INT Lo que hace es setear los bits INTE y GIE de INTCON, y dejar el resto a 0. Claro que lo pude haber hecho de esta forma: intcon = 0x00; // Limpiar INTCON intcon.INTE = 1; // Setear INTE intcon.GIE = 1; // Setear GIE o de este otro modo: intcon = 0b10010000; // Setear bits 4 y 7

De hecho, esto ltimo equivale exactamente a lo que puse en el programa. Es decir, el nmero 0b10010000 es lo mismo que (1<<INTE)|(1<<GIE). Cmo as? Pues, si ejecutamos la sentencia por partes, tenemos: Para (1<<INTE). Primero se toma un 1 (0b00000001) y luego se le desplaza INTE (4) posiciones a la izquierda. Eso dar 0b00010000. Para (1<<GIE). Primero se toma un 1 (0b00000001) y luego se le desplaza GIE (0) posiciones a la izquierda. Obviamente eso dar el mismo 0b00000001.

CURSO_MICROS

P g i n a | 158

Ahora se toman ambos resultados parciales y se les aplica un OR binario (|). Esto resultar en el nmero 0b00010001, que es finalmente el valor que se cargar en INTCON. En realidad, el compilador es ms listo de los creemos. Cuando se d cuenta de que toda esa expresin equivale a INTCON = 0b00010001, tomar este camino directamente. Aunque en un principio puede parecer una construccin mstica, yo prefiero escribir as que en binario. Ya no tengo que revisar cul era la posicin de un bit... Espero que pronto ests de acuerdo conmigo. La Interrupcin de Cambio de PORTB Esta interrupcin se dispara cada vez que se detecta un cambio de nivel lgico 1 a 0 o viceversa en cualquiera de los pines RB4, RB5, RB6 o RB7. Por eso, aunque no es propiamente reconocido, tambin se podra decir que se dispara con los flancos de subida y de bajada en dichos pines. Debemos observar que mientras la Int RB0/INT se produce solo en un flanco (aunque se puede cambiar en tiempo de ejecucin), la Int de Cambio de PORTB se da siempre en ambos flancos. Por ejemplo, al pulsar un botn podra haber dos interrupciones: una al oprimirlo y otra al soltarlo. INTCON GIE PEIE T0IE INTE RBIE T0IF INTF RBIF

Para habilitar esta interrupcin hay que setear los bits RBIE y GIE. Adems es necesario que el o los pines en cuestin estn configurados como entradas. Una vez producido el cambio de nivel en uno o varios pines de RB4 a RB7, se setear el flag RBIF y si la interrupcin est habilitada, se llamar a la funcin interrupt. Como de costumbre, RBIF debe ser limpiado por software, pero antes es imprescindible actualizar los respectivos latchs internos de PORTB que marcan si el valor actual de los pines RB4 a RB7 es diferennte de su valor previo o no. Tal vez son extrao pero hacerlo es tan simple como leer o escribir el pin que cambi o, mejor, todo el registro PORTB. Esta interrupcin tambin puede sacar al PIC del modo Standby. Prctica 3 Interrupcin de Cambio de PORTB Repetiremos la funcionalidad del programa anterior. El PIC en estado de sleep pero que est vez despierta con una Int de Cambio de PORTB para conmutar el estado de un LED. Nuestro objetivo es advertir la diferencia con repecto a la interrupcin RB0/INT.

CURSO_MICROS

P g i n a | 159

El circuito

El cdigo fuente

//************************************************************************ // FileName: TogLed.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Uso de interrupcin de Cambio de PORTB //************************************************************************ #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 //************************************************************************ // Interrupt Service Routine // Se asume que slo est habilitada la Int de Cambio de PORTB y que // sta fue disparada por un cambio en el pin RB7. //************************************************************************ void interrupt(void) { toggle_bit(porta, 0); // Bascular pin RA0 (LED azul) delay_ms(50); // Delay para pasar los rebotes portb = portb; // Limpiar condicin de interrupcin intcon.RBIF = 0; // Limpiar flag de interrupcin } //************************************************************************ // Funcin principal

CURSO_MICROS

P g i n a | 160

//************************************************************************ void main() { porta.0 = 0; // Led iniciar apagado trisa.0 = 0; // Salida para led trisb = (1<<7); // Equivale a TRISB = 0b10000000 // Slo el pin RB7 como entrada portb = portb; // Limpiar condicin de Int de Cambio de PORTB intcon = (1<<RBIE)|(1<<GIE); // Habilitar Int de Cambio de PORTB while(1) // Bucle infinito { sleep(); // No hay nada que hacer. As que... a dormir nop(); // Al despertar se ejecuta este nop y luego llama // a interrupt } } Descripcin del programa La Interrupcin de Cambio de PORTB se dispara cuando cambia alguno de los pines de RB4 a RB7 y cualquier ruido tambin podra originar este cambio. Por lo tanto, o sujetamos los pines de entrada no involucrados (RB4...RB6), o los configuramos como salida. Como ves, yo tom el segundo camino. TRISB = (1<<7); // Equivale a TRISB = 0b10000000

Siempre es ms que recomendable limpiar la condicin de esta interrupcin y su flag antes de habilitarla. No me preguntes por qu. PORTB = PORTB; // Limpiar condicin de Int de Cambio de PORTB INTCON = (1<<RBIE)|(1<<GIE); // Habilitar Int de Cambio de PORTB Limpiar la condicin de interrupcin significa actualizar los latchs internos de PORTB, que es lo que al final determina el evento de esta interrupcin. Si crees que portb = portb; es una sentencia redundante, qutala y vers cmo se cuelga tu programa ;) Por ltimo, si ya apreciaste el funcionamiento de esta prctica, por lo menos en Proteus VSM, habrs notado que el LED se prende y luego se apaga al presionar el botn. No permuta por cada pulsada. Eso es porque en cada pulsada hay dos flancos y tendremos una interrupcin en el flanco de bajada y otra en el flanco de subida. Para una mejor simulacin paso a paso te sugiero que cambies el pulsador por un switch de dos estados. Prctica 4 Interrupciones mltiples Cualquiera que sea la fuente de interrupcin, el programa llamar a la misma funcin interrupt. As mismo, para la Int de Cambio de PORTB, cualquiera que sea el pin (RB4:RB7) que cambie y cualquiera que sea la transicin de dicho cambio, se activar el mismo flag RBIF.

CURSO_MICROS

P g i n a | 161

En esta prctica aprenderemos a discernir la fuente de interrupcin y tambin a descubrir el pin y el flanco de accin en la Int de Cambio de PORTB. No esperes nada extraordinario en estos ejemplos. El programa tiene tres pulsadores conectados al puerto B: uno para la interrupcin RB0/INT y dos para la de Cambio de PORTB (para este propsito da lo mismo dos que los cuatro). Cada uno de los pulsadores debe hacer bascular un LED conectado al puerto A. En los tres casos los LEDs deben bascular solo en el flanco de bajada. El circuito

El cdigo fuente

//************************************************************************ // FileName: MulInts.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Manejo de interrupciones mltiples (RB0/INT + PORTB Change) //************************************************************************ #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 //************************************************************************ // Interrupt Service Function // Dado que hay mltiples interrupciones habilitadas, se deben testear // los Flags de Int para conocer la fuente de Int. //************************************************************************ void interrupt(void) {

CURSO_MICROS

P g i n a | 162

delay_ms(30);

// Pausa antirrebote

if(intcon.INTF == 1) // Si fue Int RB0/INT... { // porta ^= 1; // Bascular RA0 intcon.INTF = 0; // Limpiar flag } if(intcon.RBIF == 1) // Si fue Int de Cambio de PORTB... { // if(portb.7 == 0) // Si RB7 = 0 porta ^= 0x04; // Bascular RA2 if(portb.6 == 0) // Si RB6 = 0 porta ^= 0x02; // Bascular RA1 portb = portb; // Limpiar condicin de Int de Cambio de PORTB intcon.RBIF = 0; // Limpiar flag de Int de Cambio de PORTB } } //************************************************************************ // Funcin principal //************************************************************************ void main() { trisa = 0x00; // Todo PORTA salida trisb = 0b11000001; // RB0, RB6 y RB7 entradas option_reg.INTEDG = 0; // Flanco de bajada para Int RB0/INT option_reg.NOT_RBPU = 0; // Habilitar resistencias de pull-up portb = portb; // Limpiar condicin de Int de Cambio de PORTB intcon = (1<<RBIE)|(1<<INTE)|(1<<GIE); // Habilitar interrupciones // RB0/INT y de Cambio de PORTB while(1) // Bucle infinito { sleep(); // Entrar en modo Standby nop(); } } Descripcin del programa En la ISR la pausa delay_ms(30) nos hace esperar hasta que las seales en los pulsadores se estabilicen. Nota que al liberarse el botn de RB6 o RB7 (flanco de subida) tambin se disparar una interrupcin, pero sta ser descartada por las sentencias if que buscan un estado bajo. if(portb.7 == 0) // Si RB7 = 0 porta ^= 0x04; // Permutar RA2 if(portb.6 == 0) // Si RB6 = 0 porta ^= 0x02; // Permutar RA1 El operador xor ^ es una forma alternativa para bascular o conmutar el estado de un bit.

CURSO_MICROS

P g i n a | 163

Los Timers Los Timers de los PIC16F84A y PIC16F87XA Introduccin Los Timers son mdulos que trabajan en paralelo con el procesador, permitiendo que las operaciones de temporizacin y conteo se puedan llevar a cabo de manera eficiente, mientras el procesador se ocupa de otras tareas. El PIC16F84A cuenta solo con el Timer0. Los PIC16F87xA tienen adicionalmente un Timer1 y un Timer2, cuyo funcionamiento es similar al del Timer0 en cuanto a sus operaciones bsicas, es decir, aprenderemos a controlarlos con un mnimo de esfuerzo despus de aprender a manejar el Timer0. Sin embargo, a diferencia del Timer0, cuyo empleo se limita a las temporizaciones y otro tanto a los conteos, los timers 1 y 2 estn diseados ms bien para trabajar con los mdulos CCP1 y CCP2, principalmente para generar ondas PWM. El Timer0 es compatible en todo sentido en el PIC16F84A y los PIC16F87xAM (por no decir que es el mismo en todos los PIC16). En las prcticas de Timer0 de este captulo se emplea el PIC16F84A por practicidad. El Timer0 El Timer0 es un contador/temporizador de 8 bits que opera en modo incremental. Puede contar en todo su rango (desde 0 hasta 255) cclicamente, o sea, luego de llegar al tope el conteo se reinicia desde 0. Otras de sus caractersticas son: Se puede usar como Contador o Temporizador. Se representa con el registro TMR0, el cual es de lectura y escritura. Tiene un prescaler programable. Capacidad de disparar una interrupcin en el desbordamiento.

La explicacin de un mdulo se asimila mejor si nos guiamos por su diagrama de bloques.

Diagrama de bloques del Timer0.

CURSO_MICROS

P g i n a | 164

El Timer0 se incrementa con los pulsos de una seal de reloj que puede provenir de dos fuentes y que determina los dos modos de operacin del Timer0: Modo Temporizador. El reloj del Timer0 proviene del mismo oscilador interno del sistema Fosc/4. Segn la figura de arriba, este modo se selecciona si el bit T0CS = 0. Modo Contador. Aqu el reloj del Timer0 proviene del pin RA4/T0CKI. Digamos que el Timer0 contar los pulsos detectados en dicho pin. Se selecciona si el bit T0CS = 1.

Control del Timer0 El registro OPTION_REG El nombre no ayuda nada pero ste es el principal registro de control del Timer0. Aparte de los seis bits resaltados abajo solo nos faltara hablar de los bits que controlan la interrupcin del Timer0. Sabemos que ellos estn en el registro INTCON y.. volveremos al tema ms adelante. RBPU INTEDG Bit 7 RBPU: PORTB Pull-up Enable bit 1 = Inhabilitar Pull-ups de PORTB 0 = Habilitar Pull-ups de PORTB en los pines configurados como entradas En los archivos de dispositivo de Microchip este bit se llama NOT_RBPU. INTEDG: Interrupt Edge Select bit 1 = Interrupcin en flanco de subida del pin RB0/INT 0 = Interrupcin en flanco de bajada del pin RB0/INT T0CS: TMR0 Clock Source Select bit 1 = Transicin en el pin RA4/T0CKI 0 = Reloj de ciclo de instruccin interno (CLKOUT) T0SE: TMR0 Source Edge Select bit 1 = Incremento en transicin alto-a-bajo en el pin RA4/T0CKI 0 = Incremento en transicin bajo-a-alto en el pin RA4/T0CKI PSA: Prescaler Assignment bit T0CS T0SE PSA PS2 PS1 PS0 Bit 0

CURSO_MICROS

P g i n a | 165

RBPU INTEDG T0CS T0SE PSA 1 = Prescaler es asignado al WDT 0 = Prescaler es asignado al mdulo Timer0 PS2 PS1 Razn Timer0 Razn Watchdog PS0 000 001 010 011 100 101 110 111 El Prescaler del Timer0 1:2 1:4 1:8 1 : 16 1 : 32 1 : 64 1 : 128 1 : 256 1:1 1:2 1:4 1:8 1 : 16 1 : 32 1 : 64 1 : 128

PS2

PS1

PS0

El prescaler es un circuito contador por el que se puede hacer pasar el reloj del Timer0 para reducir o dividir su frecuencia (ver primera figura). De ese modo el Timer0 se incrementar ms lento. Los factores de divisin o razones de prescaler se establecen por los bits PS2, PS1 y PS0 del registro OPTION_REG, como se ve arriba, en su descripcin. Pueden ser 1:2, 1:4,..., 1:256. Por ejemplo, sabemos que en modo Contador el Timer0 se incrementa con los pulsos del pin RA4/T0CKI. Sin prescaler significa que se incrementa con cada 1 pulso. Con prescaler y una razn de 1:2 significa que se incrementa con cada 2 pulsos, y as sucesivamente. Para complicar las cosas, el prescaler del Timer0 es compartido con el Watchdog. Como se ve en El registro OPTION_REG, el prescaler establece diferentes factores de divisin si se aplica al Timer0 o al Watchdog. Por supuesto, el prescaler no se puede aplicar a los dos mdulos al mismo tiempo. Si el bit PSA = 1, el prescaler se asigna al Watchdog (opcin por defecto). Si el bit PSA = 0, el prescaler se asigna al Timer0.

Lo olvidaba, el Watchdog o WDT es un timer secundario que lo estudiamos al final.

CURSO_MICROS

P g i n a | 166

Programacin del Timer0 El Timer0 en modo Temporizador Este modo queda seleccionado cuando el bit T0CS = 0. Como siempre, TMR0 corre libremente hasta 255, tras lo cual se desborda y reinicia desde 0. Esa condicin de desbordamiento activar el bit T0IF (de INTCON). El reloj del Timer0 proviene del mismo reloj del sistema Fosc/4. Si aplicamos el prescaler al Timer0, la frecuencia de este reloj se reducir segn el factor de prescaler establecido. Por ejemplo, si fijamos una razn de prescaler de 1:2, entoces el reloj del Timer0 ser (Fosc/4)/2 = Fosc/4. Para ser concretos, si trabajamos con un XTAL de 4MHz y no usamos prescaler, entonces el Timer0 tendr un reloj de 1 MHz y el registro TRM0 se incrementar cada 1/1MHz = 1 us, lo mismo que un ciclo de instruccin bsica. Usando prescaler de 1:2, TMR0 se incrementar cada 2us; usando prescaler de 1:16, cada 16us; y as. En la prctica el uso del Timer0 como temporizador implica saber manipular su recorrido Por ejemplo, con el XTAL de 4MHz el registro TMR0 avanza desde 0 hasta 255 en 256 us como mnimo (sin prescaler). Si cargamos TMR0 con 200, llegar al desbordamiento despus de 56 us; y si usamos prescaler de 1:8, lo har despus de 568 = 448 us. Al inicio todos vemos en esto un enredo de nmeros y preferimos usar clculos directos como los presentados en la siguiente seccin. Clculo de la temporizacin La operacin por excelencia del Timer0 es como temporizador, pero el principal obstculo con que tropieza un novel diseador es el clculo de la temporizacin. Bueno, vamos al grano. El Tiempo que pasar el Timer0 contando desde un valor inicial TMR0 hasta 255 y se produzca el desbordamiento est dado por la siguiente frmula:

Donde: Tiempo = Valor de la temporizacin. Fosc = Frecuencia del XTAL del PIC16F. P = Factor de prescaler (1, 2, 4, 8, 16, 32, 64, 128 256). TMR0 = Valor de inicio del registro TMR0.

Como ves, sta es una ecuacin con dos incgnitas (P y TMR0) y es posible encontrar ms de una solucin para ambas. Sin embargo, no todas sern igualmente apropiadas. Los valores ms apropiados sern los que nos permitan realizar un mejor posterior

CURSO_MICROS

P g i n a | 167

ajuste de precisin. Si eres ducho resolviendo ecuaciones de Diofanto, puedes trabajar con esa frmula. Pero si no quieres ir tanteando, puedes emplear las siguientes dos frmulas: (He borrado todas sus deducciones por creerlas prescindibles.)

Lo ms probable es que el valor obtenido con esta frmula no est disponible como factor de prescaler vlido (1, 2, 4, 8, 16, 32, 64, 128 256). En tal caso deberemos tomar el factor superior ms cercano (redondear para arriba).

Como antes, si el resultado no fuera un nmero entero, habra que redondearlo para arriba. Si el factor de prescaler obtenido estuviera fuera del rango permitido (ms alto que 256), se puede optar por buscar otro camino, como fragmentar la temporizacin. Por otro lado, si la temporizacin es muy fina, puede que sea necesario subir un poquito el valor de inicio del TMR0 para realizar una calibracin aadiendo algunas instrucciones de relleno como nops. Estas dos situaciones las veremos en las prcticas; as que pierde cuidado si no las dej muy claro. A modo de ejemplo, hallemos el factor de prescaler P y el valor de inicio del TMR0 para generar una temporizacin de 5 ms si el PIC trabaja con un XTAL de 10 MHz.

(factor superior ms cercano). Y el valor de inicio del registro TMR0 ser:

(redondear para arriba) La secuencia de conteo resultara as:

Otro ejemplo. Cules son la razn de prescaler y el valor inicial de TMR0 para conseguir una temporizacin de 200 s si nuestro PIC tiene un XTAL de 4 MHz? El factor de prescaler P sera:

(factor superior ms cercano [sin prescaler]) Y el valor inicial del TMR0 ser:

CURSO_MICROS

P g i n a | 168

Luego, la secuencia de conteo quedara as:

Finalmente, cules son la razn de prescaler y el valor inicial del TMR0 para conseguir una temporizacin de 50 ms si se tiene un PIC con un XTAL de 20 MHz? El factor de prescaler sera:

Y ahora de dnde vamos a sacar un factor de prescaler mayor que 976.56 si el mximo es de 256? Bueno, quiz podramos temporizar 5 veces 10 ms. Tutorial Timer0 Prctica 1 Temporizacin con sondeo del Timer0 El programa hace... qu crees?... parpadear un LED. Hey! Solo es un ejemplo. El LED bascula cada 10 ms, pero como ya estamos en temas serios, la temporizacin debe ser lo ms precisa posible, ni 1 s ms ni 1 s menos. Visto de otro modo, el programa genera una seal de onda cuadrada de 50 Hz.

CURSO_MICROS

P g i n a | 169

El circuito

El cdigo fuente

//************************************************************************ // FileName: T0_Timer.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Timer0 - Operacin en modo Temporizador //************************************************************************ #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 void Pause(void); // Prototipo de funcin

//************************************************************************ // Funcin principal //************************************************************************ void main() { trisa.2 = 0; // RA2 salida de seal option_reg = 0b10000101; // OPTION_REG bitmap: // RBPU INTEDG T0CS T0SE PSA PS2 PS1 PS0 // * RBPU = 1 Inhabilitar pull-ups // * T0CS = 0 Timer0 en modo Temporizador // * T0SE = No vale en este modo // * PSA = 0 Prescaler para Timer0 // * PS2 PS1 PS0 = 101 Prescaler de 1:64

CURSO_MICROS

P g i n a | 170

while(1) // Loop forever { toggle_bit(porta, 2); // Bascular pin RA2 Pause(); // Delay de 9995 s } } //************************************************************************ // Produce 9995 s exactamente // Con el Prescaler de 1:64 el Timer0 se incrementa cada 64 s, con un XTAL de // 4MHz. Por tanto, para alcanzar 9995s se requieren de 9995/64 = 156 ticks. //************************************************************************ void Pause(void) { tmr0 = 100; // Cargar registro TMR0 intcon.T0IF = 0; // Limpiar flag del Timer0

while(intcon.T0IF==0) // Ocurri desbordamiento? continue; // No an. Seguir esperando nop(); } Descripcin del programa Veamos el punto crucial del programa: el clculo de la temporizacin. Segn mi cdigo, para que la seal cambie de nivel cada 10 ms, la funcin Pause debera tomar 9995s, ya que el bucle llega a dicha llamada cada 5 s (lo vi en el simulador). Por supuesto, este valor va a variar de un compilador a otro porque cada cual compila a su modo. Inclusive vara en un mismo compilador segn el nivel de optimizacin establecido o segn el microcontrolador usado. while(1) // Loop forever { toggle_bit(porta, 2); // Bascular pin RA2 Pause(); // Delay de 9995 s } El factor de prescaler sera: // 1 nop para precisar la temporizacin

(factor superior ms cercano) Y el valor inicial del TMR0 es:

(redondear arriba) El ajuste de la temporizacin se ha conseguido aadiendo un nop en Pause. Para calibrar estas precisiones es aconsejable recurrir al TIMER COUNTER de Proteus VSM o al Stopwatch de MPLAB IDE. Aprendimos a usarlos en el mdulo 1.

CURSO_MICROS

P g i n a | 171

Interrupcin del Timer0 La real potencia del Timer0 se deja apreciar al emplear su caracterstica ms notable: la interrupcin del Timer0. El evento que puede disparar esta interrupcin es el desbordamiento del registro TMR0, o sea, la transicin de 255 a 0. Para habilitar la interrupcin del Timer0 se setean los bits T0IE y GIE. INTCON GIE PEIE T0IE INTE RBIE T0IF INTF RBIF

El desbordamiento de TMR0 activar el flag T0IF y si la interrupcion esta habilitada, se disparar. El bit T0IF debe ser limpiado por software. En los PIC16F87xA los bits T0IE y T0IF se llaman tambin TMR0IE y TMR0IF, respectivamente. Una observacin: tanto en modo temporizador y contador el circuito del Timer0 depende del oscilador del sistema. Por tanto en modo Sleep el Timer0 no operar y tampoco producir una interrupcin. Prctica 2 Uso de la interrupcin del Timer0 Se genera una onda cuadrada de frecuencia 500 Hz. Quiz creas que no es una prctica muy provechosa pero en realidad en la gran mayora de aplicaciones el Timer0 funciona tal como se ver aqu. Depender del diseador saber qu hacer con esta seal. Por ejemplo, en la siguiente prctica generaremos una onda PWM y al mismo tiempo una seal para bascular un LED en intervalos largos de tiempo, mas all de lo que permite el prescaler.

CURSO_MICROS

P g i n a | 172

El circuito

El cdigo fuente

//************************************************************************ // FileName: T0_Interrupt.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Temporizacin por Interrupcin del Timer0 //************************************************************************ #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 //************************************************************************ // Gestor de Interrupcin. // Se da por hecho que la Interrupcin del Timer0 es la nica habilitada. // La Interrupcin se dispara cada 1000s exactamente. //************************************************************************ void interrupt(void) { nop(); // Ajuste de precisin nop(); // Ajuste de precisin tmr0 += 6; // Reponer registro TMR0

CURSO_MICROS

P g i n a | 173

toggle_bit(porta, 2); // Bascular pin RA2 intcon.T0IF = 0; } //************************************************************************ // Funcin principal //************************************************************************ void main() { trisa.2 = 0; // RA2 salida de seal option_reg = 0b10000001; // OPTION_REG bitmap: // RBPU INTEDG T0CS T0SE PSA PS2 PS1 PS0 // * RBPU = 1 No queremos pull-ups // * T0CS = 0 Timer0 en modo Temporizador // * T0SE = No vale en este modo // * PSA = 0 Prescaler para Timer0 // * PS2 PS1 PS0 = 001 Prescaler de 1:4 tmr0 = 6; // Valor inicial de TMR0 intcon = (1<<T0IE)|(1<<GIE); // Habilitar de interrupcin del Timer0 while(1) // Loop forever { nop(); } } Descripcin del programa En la tarea principal del programa no hay nada til que hacer. Puede que esto induzca al sueo al CPU; pero no debe dormir porque el Timer0 tambin lo hara. As que se le mantiene despierto haciendo nop (o nada) mientras espera las interrupciones del Timer0. Sobre la ISR. Para conseguir una frecuencia de 500 Hz cada semiperiodo de la onda debe valer 1/(5002) = 0.001 s = 1 ms = 1000 s. El factor de prescaler sera: // Limpiar flag de esta Int

(factor superior ms cercano) Y el valor inicial del TMR0 es:

Observa que en la funcin interrupt el registro TMR0 no se carga directamente como al inicio, sino que se le suma el 6. Esa operacin es ms conveniente porque compensar el tiempo que pasa desde el momento exacto en que se dispara la interrupcin hasta la recarga de TMR0. nop(); nop(); // Ajuste de precisin // Ajuste de precisin

CURSO_MICROS

P g i n a | 174

tmr0 += 6;

// Reponer registro TMR0

Aun as, la simulacin me indicaba que las interrupciones se disparaban cada 998 s. Por eso tuve que retrazarlas con dos nops. Nota que estos nops deben ir antes de reponer el TMR0. El cdigo subsiguiente a la recarga no afectar a la temporizacin en modo alguno. Esto podra ser una constante a la hora de los ajustes: agregar algunos nops en la ISR y tal vez hasta aumentar un poquito el valor de TMR0. Temporizacin con el Timer0 Prctica 3 Estirando la temporizacin Esta vez haremos bascular un LED cada 500 ms, un tiempo que a priori no se puede conseguir as nada ms. Si te parece un reto poco atractivo, le aadiremos la tarea de generar una onda PWM de 500 Hz y duty cycle variable mediante dos botones. A la salida se puede conectar otro LED o un pequeo motor DC. Tambin le podemos aadir un LCD y otros adornos pero dara pereza armar ese circuito. El circuito El IRF730 puede ser sustituido por un IRF720, un IRF540 o cualquier otro similar.

CURSO_MICROS

P g i n a | 175

El cdigo fuente

/***************************************************************************** FileName: sPWM.c Processor: PIC16F84A Compiler: BoostC Purpose: Uso de la interrupcin del Timer0 ****************************************************************************/ #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 #define MaxDuty 20 // Mximo duty cycle volatile unsigned char Duty; // Duty cycle actual /***************************************************************************** Gestor de interrupciones o ISR La interrupcin del Timer0 se dispara cada 100 s. Genera una seal PWM con una frecuencia de 500 Hz ****************************************************************************/ void interrupt(void) { static unsigned char Dgen = 1; // Duty generator. Rango = [1:MaxDuty] static unsigned int ticks = 0; if( intcon.T0IF ) // Si fue Int del Timer0 { tmr0 += 158; // Reponer registro TMR0 if (Dgen <= Duty) porta.1 = 1; // Salida de seal PWM else porta.1 = 0; // Salida de seal PWM if(++Dgen > MaxDuty)// Dgen = 1; // Rango de Dgen es [1:MaxDuty] if(++ticks == 5000) // Sig. bloque se ejecuta cada 5000100u = 500ms { toggle_bit(porta, 2); ticks = 0; } intcon.T0IF = 0; } } // Limpiar flag de Timer0

/***************************************************************************** Main Function ****************************************************************************/ void main(void) { trisa.1 = 0; // Salida de seal PWM

CURSO_MICROS

P g i n a | 176

trisa.2 = 0; Duty = 0;

// Salida LED // Rango = [0 : MaxDuty]

option_reg = 0b00001000; // OPTION_REG bitmap: // RBPU INTEDG T0CS T0SE PSA PS2 PS1 PS0 // * T0CS = 0 -> Modo Temporizador // * T0SE = -> No vale en este modo // * PSA = 1 -> Prescaler a Watchdog // Razn para Timer0 = 1:1 // * RBPU = 0 -> Habilitar weak pull-ups intcon = (1<<T0IE)|(1<<GIE); // Habilitar Int. del Timer0 while (1) { // El valor de Duty est limitado al rango [0 : MaxDuty] if(portb.0 == 0) // Botn de RB0 pulsado? { if(Duty < MaxDuty) Duty++; delay_ms(130); } else if(portb.1 == 0) // Botn de RB1 pulsado? { if(Duty > 0) Duty--; delay_ms(130); } } } Descripcin del programa El principio de operacin del Timer0 es el mismo de casi siempre. Esta vez se le configura para que dispare interrupciones cada 100 s. Ahora no interesa por qu 100 s. El hecho es que para tal cometido los clculos indicaban que la raz16n de prescaler deba ser de 1:1 (o sea sin prescaler) y el valor a recargar en TMR0 deba ser de 156. En el cdigo tuve que usar 158 para calibrar los 100s buscados. Luego estudiamos el Timer2, el cual tiene recarga automtica y no necesita este tipo de ajustes. Ahora toca pensar en la temporizacin de 500 ms. Con un XTAL de 4MHz y el mximo factor de prescaler para el Timer0 solo llegaramos a 65.536 ms, que est bastante lejos de lo que buscamos. Aunque en las mismas condiciones el Timer1 puede temporizar hasta 524.288ms, el PIC16F84A no lo tiene y tampoco es necesario usar otro timer. Lo que se hace en el programa es usar la anterior temporizacion de 100 s (puede ser otra ms ptima en otras aplicaciones) para incrementar el contador ticks. Entonces deducimos que para pasar 500 ms ticks deber llegar a 5000. Y all lo tenemos. if(++ticks == 5000) // Sig. bloque se ejecuta cada 5000100u = 500ms { toggle_bit(porta, 2); ticks = 0; }

CURSO_MICROS

P g i n a | 177

Lo hecho equivale a hacer una temporizacin grande a base de varias termporizaciones menores. Otro ejemplo podra ser temporizar 50 segundos partindolos en 50000. Tambin se pueden aadir ms contadores para multiplicar ms temporizaciones, o emplear ms variables como Duty y Dgen para sacar otros canales PWM y controlar varios motores... Las ideas sobran. No voy a explicar el algoritmo de generacin de la onda PWM porque creo que no viene al caso y porque es fcil de deducir. En un posterior captulo estudiaremos los mdulos CCPx, que pueden generar ondas PWM de alta frecuencia a nivel hardware. Una observacin final, en la prctica el delay de 130ms demora ms de 400ms. Es porque las 10 000 interrupciones/segundo del Timer0 estn robando demasiados ciclos de CPU. El Timer0 en modo Contador En el modo contador el registro del Timer0, TMR0, se incrementar con los flancos que se presenten en el pin RA4/T0CKI. T0CKI es acrnimo de Timer0 ClocK Input. Este modo queda establecido cuando el bit T0CS = 1. Segun el diagrama de bloques del Timer0, cuando el multiplexor selecciona este modo tambin hay que programar el bit T0SE (Timer0 Source Edge) para establecer si los incrementos sern en los flancos de subida o de bajada. Si T0SE = 0, Timer0 se incrementa con los flancos ascendentes del pin RA4/T0CKI. Si T0SE = 1, Timer0 se incrementa con los flancos descendentes del pin RA4/T0CKI.

Como sabemos, si el prescaler est destinado al Watchdog, la razn para el Timer 0 queda a 1:1. En el modo contador significa que el registro TMR0 se incrementa con cada flanco detectado en el pin RA4/T0CKI. Si el prescaler est designado al Timer0 con una razn de 1:2, entonces el Timer0 se incrementar con cada 2 flancos del pin RA4/T0CKI. Si la razn es de 1:4, se incrementar con cada 4 flancos, y as. Todas las caractersticas conocidas de la operacin del Timer0 en modo temporizador son tambin aplicables aqu, incluyendo la interrupcin en el desbordamiento. De hecho, tambin es posible temporizar en este modo si conectamos alguna seal de reloj en el pin RA4/T0CKI. La nica consideracin, en cualquier caso, es que la seal de RA1/T0CKI jams debera superar, o igualar siquiera, la performance del reloj del sistema Fosc, para permitir la sincronizacin entre ambas seales. Prctica 4 El Timer0 en modo Contador El Timer0 contar los flancos de bajada generados por un pulsador conectado al pin RA0/T0CKI del PIC. El valor del registro TMR0 ser enviado al Puerto B. No se pondr ningn mecanismo antirebote. As que el Timer0 se incrementar inclusive con los pulsos de los rebotes.

CURSO_MICROS

P g i n a | 178

El circuito

El cdigo fuente

//**************************************************************************** // FileName: T0_Counter.c // Processor: PIC16F84A // Compiler: BoostC // Purpose: Timer0 - Operacin en modo Contador //**************************************************************************** #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 //**************************************************************************** // Funcin principal //**************************************************************************** void main() { trisa.4 = 1; // Pin RA4/T0CKI entrada trisb = 0x00; // Todo PORTB salida option_reg = 0b10111000; // OPTION_REG bitmap: // RBPU INTEDG T0CS T0SE PSA PS2 PS1 PS0 // * RBPU = 1 Inhabilitar Pull-ups // * INTEDG = No usamos intr RB0/INT // * TOCS = 1 Timer0 en modo Contador // * T0SE = 1 Flanco de bajada de pin RA4/T0CKI // * PSA = 1 Prescaler para Watchdog // * PS2 PS1 PS0 = Razn para Timer0 = 1:1

CURSO_MICROS

P g i n a | 179

tmr0 = 0x00; while(1) { portb = tmr0; } }

// Iniciar la cuenta en 0 // Loop forever // Valor de TMR0 a PORTB

El Timer2 de los PIC16F El Timer2 Continuamos con el Timer2, y no con el Timer1, porque es el ms simple de todos. No hemos empezado con l porque quera darle una reconocida despedida al PIC16F84A. As es, aqu comenzamos oficialmente con los PIC16F87xA. El Timer2 es un temporizador (solo temporizador) de 8 bits. Su fuente de reloj proviene del reloj del sistema Fosc/4, pasando previamente por el prescaler. Adems de un prescaler, tiene un postscaler. Tambin cuenta con un bit de activacin/desactivacin. Para entender lo bsico de la operacin del Timer2 hagamos el siguiente parangn: para temporizar con el Timer0 cargamos en su registro TMR0 un valor x y luego esperamos a que se desborde (despus de llegar a 255) para que dispare una interrupcin si fue programada. El Timer2 en cambio temporiza contando desde 0 hasta un valor x que nosotros cagamos en el registro PR2. Despus de llegar a este valor el registro TMR2 se resetea automticamente y vuelve a contar desde 0. Las interrupciones se pueden dar despus de una o varias coincidencias entre TMR2 y PR2. El reinicio automtico del registro TMR2 permite que las temporizaciones calculadas sean siempre precisas sin modificar los ajustes, sin importar para qu PIC se programe o qu lenguaje o compilador se use. Registros del Timer2 Los registros relacionados con las interrupciones se describen ms adelante.

TMR2. Es el registro de conteo del Timer2. PR2. Es el registro que establece el lmite hasta dnde se incrementar el TMR2. T2CON. Es el registro que controla la operacin del Timer2.

T2CON determina solo tres parmetros: el valor de prescaler, el valor del postscaler y el bit de habilitacin/inhabilitacin. Todos los bits son lectura y escritura e inicializan a 0 tras un reset POR o BOR.

CURSO_MICROS

P g i n a | 180

T2CON --Bit 7 TOUTPS3: TOUTPS0:

TOUTPS TOUTPS TOUTPS TOUTPS TMR2ON T2CKPS1 T2CKPS0 3 2 1 0 Bit 0 Timer2 Ouput Postscale Select bits 0000 = 1 : 1 postscaler 0001 = 1 : 2 postscaler 0010 = 1 : 3 postscaler 0011 = 1 : 4 postscaler . . . 1111 = 1 : 16 postscaler Timer2 On bit 1 = Timer2 ON 0 = Timer2 OFF

TMR2ON:

T2CKPS1: T2CKPS0:

Timer2 Clock Prescale Select bits 0 0 = Prescaler es 1 0 1 = Prescaler es 4 1 x = Prescaler es 16

Programacin del Timer2 El Prescaler y el Postscaler del Timer2 Como se ve en la siguiente figura, el reloj del Timer2 proviene del oscilador del sistema Fosc/4 y pasa por un Prescaler que divide la frecuencia de esta seal por 1, 4 16. El Postscaler es un circuito colocado a la salida del comparador, es decir, cuenta las veces que el registro TMR2 coincide con el registro PR2. El bit TMR2IF se setea despus del nmero de coincidencias establecido por factor de postscaler. Por ejemplo, si ponemos un factor de 1:10, el bit TMR2IF se activar despus de 10 coincidencias.

CURSO_MICROS

P g i n a | 181

Diagrama de bloques del Timer2. Interrupcin del Timer2 La activacin del bit TMR2IF puede disparar la interrupcin del Timer2 si est habilitada. El flag TMR2IF se debe limpiar por software. La interrupcin del Timer2 se habilita seteando los bits TMR2IE, PEIE y GIE. INTCON GIE PIE1 PSPIE PIR1 PSPIF PEIE ADIE ADIF T0IE RCIE RCIF INTE TXIE TXIF RBIE SSPIE SSPIF T0IF CCP1IE CCP1IF INTF TMR2IE TMR2IF RBIF TMR1IE TMR1IF

Clculo de la temporizacin La frmula que mide el tiempo que debe pasar para que el flag TMR2IF se active y pueda disparar una interrupcin es:

Donde: Tiempo = Valor de temporizacin. Prescaler = Factor del Prescaler (1, 4 16). PR2 = Valor del registro PR2 (entre 0 y 255). Fosc = Frecuencia del XTAL usado por el PIC16F. Postscaler = Factor de Postscaler (entre 1 y 16).

sta es una ecuacin de tres incgnitas. Como con el Timer0, tambin se podran deducir frmulas para cada variable. Pero con tres frmulas el atajo nos podra resultar ms largo que el camino principal. As nos quedamos aqu. Practiquemos con un ejemplo para demostrar que no es tan difcil. Hallemos los valores del Prescaler, Postscaler y del registro PR2 para que el Timer2 pueda generar interrupciones cada 1 ms. Haremos los clculos para un XTAL de 4 MHz.

CURSO_MICROS

P g i n a | 182

Despus de simplificar queda:

Empezaremos por el Prescaler porque solo hay tres posibilidades para l (1, 4 y 16). Si Prescaler = 1 y despejamos PR2, tenemos:

Los valores de Postscaler (entre 1 y 16) que hacen que la divisin sea exacta y d valores de PR2 menores que 256 son 4, 8 y 10: Si Postscaler = 4, PR2 = 249, Prescaler = 1 Si Postscaler = 8, PR2 = 124, Prescaler = 1 Si Postscaler = 10, PR2 = 99, Prescaler = 1

Hasta aqu ya tenemos nuestro problema ms que resuelto, pero solo para saber a dnde se llega tomando los prescalers de 1:4 y 1:16 he realizado los clculos y he podido obtener estas otras 4 soluciones: Prescaler = 4, Postscaler = 1, PR2 = 249 Prescaler = 4, Postscaler = 2, PR2 = 124 Prescaler = 4, Postscaler = 5, PR2 = 49 Prescaler = 4, Postscaler = 10, PR2 = 24

Las siete soluciones son igualmente vlidas. Nota: como los clculos fueron precisos, no se requerir de posteriores ajustes. //////////////////////////////////////////////////////////////////////////////// // FileName: timer2.c // Processor: PIC16F87xA // Compiler: BoostC // Purpose: Temporizacin con el TIMER2 //////////////////////////////////////////////////////////////////////////////// #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 volatile unsigned char ticks_ms; volatile unsigned char ticks_100ms; //****************************************************************************

CURSO_MICROS

P g i n a | 183

// ISR para Interrupcin del Timer2. // Se dispara cada 1 ms. //**************************************************************************** void interrupt(void) { static unsigned char i; if(pir1.TMR2IF) { // Este bloque se ejecuta cada 1 ms ticks_ms++; if(++i == 100) { // Este bloque se ejecuta cada 100 ms ticks_100ms++; i = 0; } pir1.TMR2IF = 0; // Limpiar flag } } //**************************************************************************** // Funcin principal //**************************************************************************** void main(void) { trisb.1 = 0; // Hacer salida portb.1 = 0; // Apagar RB1 option_reg.NOT_RBPU = 0; // Habilitar Pull-ups

// Configurar Timer2: // T2CON bitmap => TOUTPS3 TOUTPS2 TOUTPS1 TOUTPS0 TMR2ON T2CKPS1 // TOUTPS3:TOUTPS0 = 0011 => Postcaler = 1:4 // TMR2ON = 0 => Timer2 detenido // T2CKPS1:T2CKPS0 = 00 => Prescaler = 1:1 t2con = 0b00011100; pr2 = 249; // Lmite de TMR2 pie1.TMR2IE = 1; // Habilitar intcon.PEIE = 1; // interrupcin intcon.GIE = 1; // del Timer2 while(1) { while(portb.0 == 1) // Esperar a RB0 = 0 continue; portb.1 = 1; // Activar RB1 tmr2 = 0; // Resetear registro TMR2 pir1 = 0; // Limpiar flags de PIR1 t2con.TMR2ON = 1; // Arrancar Timer2 ticks_100ms = 0; do { // Hacer algunas cosas aqu durante 5 segundos.

CURSO_MICROS

P g i n a | 184

// Significa esperar a que ticks_100ms = 50 (50100ms = 1000ms) nop(); }while(ticks_100ms < 50); portb.1 = 0; // Apagar RB1 t2con.TMR2ON = 0; // Detener Timer2 } } El Timer1 de los PIC16F El Timer1 El mdulo Timer1 es un contador/termporizador de 16 bits. Trabaja con dos registros de 8 bits, TMR1H y TMR1L, que juntos se incrementan desde 0x0000 hasta 0xFFFF, tras lo cual se desbordan y reinician desde 0x0000. El desbordamiento activa el bit TMR1IF, evento que se puede aprovechar para generar interrupciones. Tambin cuenta con un prescaler programable y un bit de marcha/parada. El Timer1 y el Timer2 tienen algunas funcionalidades extra cuando trabajan junto con los mdulos CCPx. Ninguna de ellas ser mencionada en esta exposicin. Registros del Timer1 Los registros de control de interrupcin del Timer se describen por separado. TMR1H. Contiene el byte alto del Timer1. TMR1L. Contiene el byte bajo del Timer1. T2CON. Establece en modo de operacin del Timer1. --- T1CKPS1 T1CKPS0 T1OSCEN T1SYNC TMR1CS TMR1ON Bit 0

T1CON --Bit 7 T1CKPS1: T1CKPS0:

Timer1 Input Clock Prescale Select bits

1 1 = 1 : 8 prescaler 1 0 = 1 : 4 prescaler 0 1 = 1 : 2 prescaler 0 0 = 1 : 1 prescaler T1OSCEN: Timer1 Oscillator Enable Control bit 1 = Habilitar el oscilador interno

CURSO_MICROS

P g i n a | 185

T1CON --T1SYNC:

--- T1CKPS1 T1CKPS0

T1OSCEN

T1SYNC

TMR1CS

TMR1ON

0 = Inhabilitar el oscilador interno Timer1 External Clock Input Synchronization Control bit Vlido solo si TMR1CS = 1: 1 = No sincronizar la entrada del reloj externo 0 = Sincronizar la entrada del reloj externo TMR1CS: Timer1 Clock Source Select bit 1 = Reloj externo del pin RC0/T1OSO/T1CKI (en el flanco de subida) 0 = Reloj interno de Fosc/4 TMR1ON: Timer1 On bit 1 = Habilitar el Timer1 0 = Detener el Timer1 Modos de operacin del Timer1 Vamos a apoyarnos en el diagrama de bloques del mdulo mostrado abajo, que all se resume todo.

Diagrama de bloques del Timer1. Aparte de los bits de prescaler y habilitacin del Timer1, que no requieren explicacin, los tres bits restantes TMR1CS, T1OSCEN y T1SYNC establecen parmetros con los que se pueden conformar ms modos de operacin del Timer1 que los tres que se describen a continuacin. Ellos son los que para fines prcticos tendrn mayor importancia.

CURSO_MICROS

P g i n a | 186

Modo Temporizador. El reloj del Timer1 proviene del oscilador interno Fosc/4. Este modo queda seleccionado poniendo el bit TMR1CS = 0 (modo Temporizador). El bit T1SYNC = x, o sea, no importa porque Fosc est siempre sincronizado consigo mismo y el bit T1OSCEN es preferible dejarlo en 0 porque no hace falta habilitar el oscilador para XTAL externo. Modo Contador Sncrono. Aqu el reloj del Timer1 proviene del pin T1CKI (Timer1 Clock Input). El Timer1 se incrementar con los flancos de subida de dicho pin. Una ver habilitado, el Timer2 debe recibir un flanco de bajada antes de que se empiece a incrementar. Este modo queda seleccionado poniendo los bits TMR1CS = 1 (modo Contador), T1SYNC = 0 (sincronizar flancos con reloj interno) y T1OSCEN = 0 (inhabilitar oscilador para XTAL externo). Modo Contador Asncrono. Este modo resulta til para conectar un XTAL externo entre los pines RC0/T1OSO y RC1/T1OSI. El PIC tiene entre estos pines un circuito oscilador que puede trabajar con cristales de hasta 200 kHz, aunque est optimizado para 32 kHz, los que suelen usar los dispositivos de reloj. Para trabajar de este modo hay que poner los bits TMR1CS = 1 (modo Contador), T1SYNC = 1 (no conviene sincronizar con reloj interno) y T1OSCEN = 1 (habilitar oscilador de XTAL externo).

Interrupcin del Timer1 En cualquier modo de operacin el par de registros del Timer1 se incrementan hasta llegar a 65535 (0xFFFF) y luego se desbordan para empezar otra vez desde 0. El desoboramiento activa el flag TMR1IF y es el evento que puede disparar la interrupcin. El flag TMR1IF se limpia por software. La interrupcin del Timer1 se habilita seteando los bits TMR1IE, PEIE y GIE. INTCON GIE PEIE PIE1 PSPIE ADIE PIR1 PSPIF ADIF T0IE INTE RCIE TXIE RCIF TXIF RBIE SSPIE SSPIF T0IF INTF RBIF CCP1IE TMR2IE TMR1IE CCP1IF TMR2IF TMR1IF

Al no depender de Fosc, en modo Contador Asncrono el Timer1 podra seguir operando y dispararando sus interrupciones incluso si entra en modo Sleep. En los otros casos el modo Sleep detentr el Timer1. Clculo de la temporizacin Ya sea que el Timer1 temporice en base al reloj del sistema o su cristal externo, el tiempo que pasar para que el par TMR1H:TMR1L se desborde y pueda provocar una interrupcin se clcula con la frmula:

CURSO_MICROS

P g i n a | 187

Donde: Prescaler = Factor del Prescaler (1, 2, 4 u 8) TMR1 = Valor a cargar en el par de registros TMR1H:TMR1L (entre 0 y 65535). Fosc/ext = Frecuencia del XTAL principal o del conectado a los pines RC1 y RC0. Tiempo = Tiempo de desbordamiento.

Acceso a los registros del Timer1 El bus de datos de 8 bits del PIC no le permite acceder a los registros TMR1H:TMR1L al mismo tiempo. El problema con esto es que existe el riesgo de que uno de los registros cambie mientras se accede al otro. Esto es especialmente crtico en el caso de las lecturas cuando el Timer1 opera asncronamente. En los PIC18F el Timer1 cuenta con un buffer de apoyo que le permite leer y escribir los 16 bits en una sola instruccin. En los PIC16F hay que seguir ciertos procedimientos como los sugeridos en el Manual de Referencia de los PICmicro de la Familia MidRange. Mejor vamos a la prctica. Prctica 6 El Timer1 en modo temporizador Veremos cmo realizar realizar una temporizacin de 500 ms con el Timer1. El circuito

CURSO_MICROS

P g i n a | 188

El cdigo fuente

//////////////////////////////////////////////////////////////////////////////// // File Name: t1_timer.c // Processor: PIC16F87xA // Compiler: BoostC // Purpose: Uso del Timer1 en modo Temporizador //////////////////////////////////////////////////////////////////////////////// #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 void Timer1_write(int); // Interrupcin del Timer1 se dispara cada 500 ms exactamente. void interrupt(void) { if(pir1.TMR1IF) { Timer1_write(3039); // Recargar registro del Timer1 toggle_bit(portb, 1); // Bascular pin RB1 pir1.TMR1IF = 0; // Limpiar flag } } void main(void) { trisb.1 = 0; // Hacer RB1 salida // Configurar el Timer1 en modo Temporizador y con prescaler de 1:8 t1con = 0x31; Timer1_write(3039); pie1.TMR1IE = 1; // Habilitar intcon.PEIE = 1; // interrupcin intcon.GIE = 1; // del Timer1 while(1); } void Timer1_write(int data) { tmr1l = 0x00; // Para prevenir desbordamiento al acceder TMR1H tmr1h = (char)(data>>8); tmr1l = (char)data; } Descripcin del programa Con tantas prcticas de temporizaciones creo que ya estamos en condiciones de realizar los clculos siguiendo deducciones lgicas. // Esperar interrupciones

CURSO_MICROS

P g i n a | 189

Con el XTAL de 4 MHz el reloj del sistema es Fosc/4 = 1 MHz, seal que va al reloj del Timer1 para hacerlo incrementar cada 1/1MHz = 1 us. Si el Timer1 recorre todo lo que puede (0-65 535), tardara 65.536ms en desbordarse. Me sigues? Como el tiempo que buscamos, 500ms, es ms de 7 veces mayor que 65.536ms, escogemos un factor de prescaler superior ms cercano, o sea 8, que felizmente est disponible. Con prescaler de 1:8 el Timer1 se incrementa cada 8us, es decir, para llegar a nuestros 500ms necesitamos 500 000us/8us = 62 500 incrementos. Como el Timer1 avanza hacia arriba, para dar 62 500 incrementos antes de desbordarse tendr que empezar desde 65 536 - 62 500 = 3 036. El clculo exacto debera suponer que no har falta un ajuste de la precisin. Pero no: desde que se produce el desbordamiento hasta el momento exacto en que se carga el segundo registro del Timer1 hay un tiempo que compensar. Con ayuda de un simulador, establec que deba recargar Timer1 con 3039. Prctica 8 El Timer1 con XTAL externo Cuando el Timer1 obtiene su fuente de reloj del XTAL colocado entre los pines RC0 y RC1 se llama modo Contador (Asncrono), a pesar de que tendr una funcin de temporizador. Por todas sus caractersticas es un modo pensado para implementar un reloj. En esta prctica usaremos un XTAL de 32kHz para generar interrupciones cada 500 ms, lo necesario para empezar por el segundero de un reloj/calendario, aunque de momento solo veremos la seal en un LED. El circuito

CURSO_MICROS

P g i n a | 190

El cdigo fuente

//////////////////////////////////////////////////////////////////////////////// // File Name: t1_xtal.c // Processor: PIC16F87xA // Compiler: BoostC // Purpose: Uso del Timer1 en modo Contador Asncrono con XTAL externo //////////////////////////////////////////////////////////////////////////////// #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 void Timer1_write(int); // Interrupcin del Timer1 se dispara cada 1000 ms exactamente. void interrupt(void) { if(pir1.TMR1IF) { Timer1_write(49152); // Recargar registro del Timer1 toggle_bit(portb, 1); // Bascular pin RB1 pir1.TMR1IF = 0; // Limpiar flag } } void main(void) { trisb.1 = 0; // Hacer RB1 salida // Configurar el Timer1 en modo Contador Asncrono y con prescaler de 1:1 t1con = 0x0F; Timer1_write(49152); pie1.TMR1IE = 1; // Habilitar intcon.PEIE = 1; // interrupcin intcon.GIE = 1; // del Timer1 while(1) { sleep(); nop(); } }

// Entrar en modo Sleep

void Timer1_write(int data) { tmr1l = 0x00; // Para prevenir desbordamiento al acceder TMR1H tmr1h = (char)(data>>8); tmr1l = (char)data; }

CURSO_MICROS

P g i n a | 191

Descripcin del programa Con los 32768 pulsos/segundo del XTAL de 32.768 kHz el Timer1 abarcar todo su recorrido (0-65535) en 2 segundos. Como nosotros queremos 500 ms, haremos que el Timer1 se incremente solo la cuarta parte, esto es, 65536/4 = 16384. Como el Timer1 avanza hacia arriba, para dar 16384 incrementos antes de desbordarse tendr que empezar desde 65536 16384 = 49152. El USART: Interface RS232 > El estandar RS232 Introduccin Despus de utilizar los displays de 7 segmentos y los displays LCD como interfaces para nuestras aplicaciones ha llegado la hora de dar un paso ms adelante y qu mejor que la misma pantalla de un computador. La interface con un ordenador se puede realizar por cualquiera de sus puertos externos ms conocidos: serie, paralelo o el USB. El paralelo casi ni se encuentra en los ordenadores de hoy y de momento el puerto USB nos queda fuera de alcance por la complejidad del desarrollo del firmware (programa del microcontrolador). As nos quedamos con el puerto serie. Aprenderemos a volcar datos desde nuestro microcontrolador a la pantalla del ordenador (data logging), as como a enviar datos mediante el teclado del PC hacia el microcontrolador. El Estndar RS-232 Toda comunicacin elaborada entre dos dispositivos requiere conocer el protocolo que la gobierna a nivel hardware y software. Para el puerto serie se trata del Estndar RS-232, o ms bien EIA/TIA-232 por las siglas de Electronics Industry Association y Telecommunications Industry Association, sus desarroladores El RS-232 fue originariamente pensado para regir las comunicaciones entre ordenadores y equipos de mdem de la poca (hace ms de 40 aos). Con el tiempo han surgido otras versiones como RS-232-C, RS-232-D, RS-232-E, etc., una ms reciente que la otra, pero con variaciones inapreciables por ser uno de los estndares menos estrictos. Despus de todo, es solo un Estndar Recomendado o Recommended Standard; de ah la RS. En la literatura tcnica se acostumbra mucho utilizar los trminos DTE y DCE para referir a los dispositivos que se comunican segn el Estndar RS-232. DTE (Data Terminal Equipment) suele representar al ordenador y DCE (Data Circuit-terminating Equipment) designa a cualquier dispositivo conectado al ordenador (un mdem se sobrentenda antes). Sin embargo, estos conceptos no quedan del todo claros en redes del tipo ordenadorordenador o microcontrolador-microcontrolador usando el puerto serie. As que por comodidad en adelante hablaremos de ordenador y mdem, viendo como mdem hasta donde quepa a cualquier dispositivo conectable al puerto serie (el circuito de nuestro microcontrolador).

CURSO_MICROS

P g i n a | 192

Ahora pasemos a describir los principales aspectos que nos recomienda el estndar. Voltajes de los niveles lgicos del RS-232 En las comunicaciones seriales RS-232 los valores para representar los 1s y 0s lgicos son muy diferentes de los que estamos acostumbrados a usar en el mundo TTL. All no existen los 5V (para el 1) y 0V (para el 0). Para entenderlo ms fcilmente veamos la siguiente figura, donde se compara la forma de onda de una seal RS-232 con la forma de onda de una seal digital convencional.

Niveles de tensin para los 1s y 0s lgicos. Puedes notar la enorme diferencia: los 1 lgicos se representan con voltajes negativos y los 0 lgicos, por voltajes positivos; adems del amplio rango de los voltajes. Un 1 lgico se expresa por una tensin de 5V a 15V. Este estado se llama spacing. Un 0 lgico se da cuando la tensin en cualquiera de las lneas es de +5V hasta +15V. Este estado se conoce como marking.

Formato de transferencia de datos Como en toda comunicacin serial, los datos viajan en grupos de bits. En este caso cada grupo o carcter consta de un bit de Start, los bits de Datos (8 por lo general), un bit de Paridad (opcional) y finaliza con uno o dos bits de Stop.

CURSO_MICROS

P g i n a | 193

Formato de un byte de dato en el Estndar RS-232. Bit de Start. Es la transicin de 1 a 0 e indica el inicio de una transferencia. En la lgica RS-232 podra significar una transicin de -15V a +15V y en lgica TTL es una transicin de 5V a 0V. Bits de Datos. Forman los datos en s que se desean transmitir. Cada dato puede ser de 5, 6, 7 u 8 bits. Por supuesto, siempre preferimos trabajar con 8 bits (1 byte). El primer bit a transmitir es el menos significativo o LSbit (Least Significant Bit). Bit de Paridad. Este bit es opcional y se puede enviar despus de los bits de datos. Sirve para ayudar a detectar posibles errores en las transferencias de datos. Es muy raramente usado, primero, porque es poco efectivo (solo podra detectar errores, no corregirlos) y, segundo, porque hay mejores formas de tratamiento de errores. Bits de Stop. Los bits de Stop son estados de 1 lgico. El Estndar dice que puede haber 1, 1.5 2 bits de Stop al final de los datos (o del bit de paridad si lo hubiera).

Velocidad de transmisin (Baud Rate) El Baud Rate es el nmero de bits que se transmiten por segundo. Debido a que estamos hablando de un tipo de transmisin asncrona, no existe una seal de reloj que sincronice los bits de datos. Para que los dispositivos transmisor y receptor se entiendan correctamente tambin es necesario que operen con el mismo baud rate. Los valores ms comunes que fija el Estndar RS-232 son: 1200, 2400, 4800, 9600, 19200, 38400, 56000, 57600, 115200, 128000, 256000. Aunque las versiones ms recientes del Estndar ponen un lmite de 20 kbits, es comn emplear los valores altos como 115200 (siempre que sea posible). Sabemos que no es lo mismo interfacear un ordenador con un microcontrolador usando un cable de 2 m de longitud que conectarlo a un PLC a 8 m de distancia: la longitud del cable y la interferencia presente en el ambiente son factores a considerar a la hora de escoger el baud rate. El puerto serie Seales Internamente el puerto serial de un ordenador es controlado por un circuito integrado (por ejemplo el 16750, de 40 pines). De esas lneas solo 9 salen al exterior y desembocan en un conector DB9 macho (el que nosotros vemos y donde conectamos nuestro programador TE-20). Raras veces se ve que salen ms lneas para llegar a un conector DB25. El uso de las 9 seales tiene ms sentido cuando se trabaja con un mdem. Por eso vamos a seguir hablando de mdem, pese a que bien puede ser reemplazado por otro dispositivo.

CURSO_MICROS

P g i n a | 194

Pines del conector DB9 (macho) del puerto serie. En la figura mostrada las direcciones de las flechas sealan si los pines son de entrada o de salida. Del mismo modo, los colores ayudan a asociar los pines con funciones anlogas o complementarias, as: TD y RD se encargan de transmitir y recibir los datos, respectivamente. RTS y CTS sirven para controlar el Control del flujo de datos (Handshaking) hardware. DTR, DSR y DCD intervienen en el establecimiento de la comunicacin.

Adems de ellos, estn la infaltable tierra (SG) y RI, usada exclusivamente en conexiones con un mdem. Ahora bien, cuando vamos a conectar el ordenador a un microcontrolador nuestro inters se puede reducir a tres lneas: TD, RD y SG. Las dems: o pueden ignorarse, o pueden conectarse al mismo puerto serie artificiosamente para evitar problemas de comunicacin, o pueden usarse para implementar un Control del flujo de datos (Handshaking) hardware, con el microcontrolador emulando algunas funciones de mdem. En cualquiera de los tres casos, eso depender del software de ordenador usado para controlar el puerto serie. En el conector hembra la posicin de los pines se puede ver diferente a fin de establecer una conexin cruzada, es decir, para que el TD de un dispositivo se una con el RD del otro y viceversa. Lo mismo debe pasar con los pares RTS-CTS y DTR-DSR. Control del flujo de datos (Handshaking) Generalmente el ordenador superar a su interlocutor (mdem u otro) tanto en velocidad de transferencia como en los buffers de recepcin de datos. Para que el mdem no empiece a perder los datos llegados el Estndar contempla mecanismos de control de flujo de datos ya sea va hardware o software. El control de flujo de datos por software se identifica por el uso de los caracteres Xon (ascii 0x11) y Xoff (ascii 0x13). El dilogo es as: cuando, por alguna razn, el mdem ya no desea recibir ms datos del ordenador entonces le enva el carcter Xoff dicindole que suspenda la transmisin al menos temporalmente. Cuando el mdem est nuevamente dispuesto a aceptar ms datos le enviar el carcter Xon y el ordenador reanudar la transferencia.

CURSO_MICROS

P g i n a | 195

Lo bueno del mtodo es que el hardware requerido es el mnimo (ver la siguiente figura) y lo malo es que ambos dispositivos deben soportar las transferencias full duplex que exige el estndar RS232 para este caso.

Conexin bsica para el Handshaking software. En un tipo de comunicacin simplex el protocolo es ste: cuando el ordenador quiere enviar datos al mdem pone un 1 en RTS. Si el mdem est dispuesto a recibir esos datos, le responder con un 1 por la lnea CTS y el ordenador empezar a transmitir los datos; de otro modo, le responder con un 0 y el ordenador tendr que posponer el envo de datos. Cuando la comunicacin es half duplex o full duplex el protocolo vara, pero eso ya no lo tocaremos aqu para no seguir enredndolo. Solo vemos lo suficiente del estndar, en este caso para entender las conexiones alternativas entre un ordenador y un microcontrolador que se usan en algunas ocasiones y que veremos en la siguiente seccin.

Cableado para el handshaking hardware entre dos dispositivos. En el diagrama se han sumado las lneas DTR, DSR y DCD (de color marrn), por las que los dispositivos se informan el uno al otro si estn listos o no para iniciar la comunicacin. Interface serial microcontrolador-ordenador Como siempre, el enfoque se divide en dos partes:

CURSO_MICROS

P g i n a | 196

Requerimientos hardware Nos vamos a enfocar en dos aspectos. Primero veamos el tema del transceiver. Dado que los niveles de tensin en el Estndar RS-232 (de 12V, 0V y +12V en el ordenador) no son compatibles con los niveles habituales de los microcontroladores (de 0 y 5V), se requiere de un transceiver que convierta estas tensiones de unos niveles a otros y viceversa. Sin duda, el MAX232 es el ms famoso de todos. Como se ve en su esquema, mostrado abajo, el MAX232 puede trabajar con una fuente de alimentacin de 5V y provee dos canales de transmisin y dos de recepcin, aunque solo se suele usar un par. A su gran tamao se suma como desventaja el uso de condensadores externos, para bombear la carga necesaria en los circuitos doblador e inversor de voltaje.

Interface entre un microcontrolador y un computador mediante el transceiver MAX232. El mismo fabricante del MAX232, Dallas Semiconductors, ofrece sus versiones mejoradas como el MAX203, que no requiere de capacitores externos, o el MAX202, que brinda proteccin contra cargas electrostticas. Mejor aun para pequeos circuitos sera el DS275 (de 8 pines), el cual tampoco requiere de capacitores externos y cuenta con el par justo de drivers de transmisin y recepcin de datos. Su principal inconveniente es que est diseado para operar solo en transferencias half duplex. Para conocer ms del funcionamiento interno de los transceivers es recomendable que consultes sus respectivos datasheets.

CURSO_MICROS

P g i n a | 197

Interface entre un microcontrolador y un computador mediante el transceiver DS275. El segundo aspecto hardware que interesa es el relacionado con el Control del flujo de datos (Handshaking): en los dos esquemas presentados anteriormente las retroconexiones en el conector DB9 (de color violeta) son opcionales. Solo harn falta cuando el programa terminal del ordenador est configurado para utilizar los pines indicados, as: RTS (7) se conecta a CTS (8) para que siempre que el ordenador desee enviar datos al microcontrolador, se responda a s mismo con un permiso concedido. Anlogamente, DTR (4) se une a DSR (6) para que cuando el ordenador informe un estoy listo para la comunicacin, su eco (hacindose pasar por el microcontrolador) le responda con un yo tambin lo estoy. A veces DTR tambin se dirige a DCD (1). Requerimientos software Por un lado necesitamos unas rutinas para el microcontrolador que gestionen las funciones del Estndar RS-232. stas pueden implementarse tranquilamente a nivel software debido a su simplicidad o mediante el mdulo USART, el cual por supuesto ofrecer mucha ms eficiencia y flexibilidad. Por otro lado, necesitaremos un programa de ordenador que se encargue de controlar su puerto serie. A su vez, este programa puede ser uno desarrollado por nosotros mismos, que nos permitira tener el control total del puerto serie y podramos transmitir y recibir todo tipo de datos (binarios o de texto). Tambin podramos implementar tcnicas alternativas de control de flujo de datos (aparte de los descritos arriba), o sofisticados mecanismos para el control de errores en la transferencias de datos. Como ves, se ve muy atractivo, pero tambin requiere de conocimientos a mediano nivel sobre programacin en lenguajes como Visual C++, Delphi o Visual Basic. Como alternativa prctica, podemos usar softwares como el Hyperterminal de Windows, Serial Port Monitor o Smart Term. Estos son programas de tipo consola que nos permiten visualizar los datos que se transfieren hacia/desde el puerto serie. Por no ofrecer tanta flexibilidad nos limitaremos a trabajar con datos de texto. Conforme vamos escribiendo los caracteres en la consola, se irn enviando hacia nuestro microcontrolador. As mismo, los caracteres enviados desde el microcontrolador se irn mostrando en la consola, todo en tiempo real. ??

CURSO_MICROS

P g i n a | 198

Interface del programa Smart Term. El USART de los PICs USART es la sigla de Universal Synchronous Asynchronous Receiver Transmitter. Es el mdulo perifrico que incorporan muchos microcontroladores para comunicarse con dispositivos que soportan el estndar RS-232. En su nombre se aprecia que puede trabajar en modo Sncrono o Asncrono. En este presentacin nos enfocaremos exclusivamente al modo asncrono. En los PICs las principales caractersticas del USART son: Asncronamente el USART puede trabajar en modo full-duplex, esto es transmitir y recibir datos, al mismo tiempo inclusive. El USART puede generar interrupciones al recibir datos o despus de enviarlos. El USART opera detrs del escenario para que las transferencias de datos se lleven a cabo mientras el CPU realiza otras tareas. El baud rate es configurable por el usuario. Los datos pueden ser de 8 9 bits, solo habr 1 bit de stop y sin bit de paridad.

Por fortuna, el USART y su control es compatible en todos los PICs que lo tienen, desde los PIC16F hasta los PIC18F, incluso hay un gran parecido con los de otros microcontroladores. As que creo que valdr la pena demorarnos un poco en esta teora. Los registros del USART Adems de estos registros todava faltan por citar los que controlan las interrupciones del USART. Aunque ya los conocemos basante bien los veremos por separado. SPBRG. Registro Generador de Baud Rate del USART. Pone la velocidad de transmisin. TXREG. Registro de Transmisin del USART. Aqu se cargan los datos a transmitir. RCREG. Registro de Recepcin del USART. Aqu se almacenan los datos recibidos. TXSTA. Registro de Control y Estado de Transmisiones. RCSTA. Registro de Control y Estado de Recepciones.

Todos estos registros son de 8 bits. Los tres primeros no tienen formatos preestablecidos y podran aceptar cualesquiera valores. Los dos ltimos tienen los siguientes mapas de bits:

CURSO_MICROS

P g i n a | 199

TXSTA RCSTA

CSRC SPEN

TX9 RX9

TXEN SREN

SYNC CREN

--BRGH ADDEN FERR

TRMT TX9D OERR RX9D

En lo sucesivo iremos describiendo las funciones de estos dos registros y de cada uno de sus bits. Algunos bits estn relacionados con la operacin del USART en modo sncrono, tema que de momento no nos compete, y sern ignorados. Ahora que si no quieres seguir esperando para ver tu PIC conectado al puerto serie del ordenador, ya puedes pasar a las prcticas. Programacion del USART Inicializacin del USART Lo primero que se debe hacer con un mdulo es configurar su operacin. Los bits de los registros TXSTA y RCSTA relacionados con esta tarea son: SYNC. Establece si el USART trabajar en modo sncrono (si vale 1) o asncrono (si vale 0). SPEN (Serial Port Enable). Con 1 se habilita el USART, con 0 se le inhabilita. Una vez habilitado, el USART asumir el control de los pines TX y RX. los cuales en los PIC16F87xA son RC6 y RC7. TXEN (Transmit Enable). Con 1 se habilita la parte del USART que transmite los datos. Con 0 la inhabilitamos. CREN (Continue Receive Enable). Lo mismo que TXEN pero para las recepciones. TX9 (9-bit Transmit). Si vale 1, los datos que se transmitan sern de 9 bits. Si vale 0 los datos sern de 8 bits. RX9 (9-bit Receive). Lo mismo que TX9 pero para las recepciones.

Con todo esto ya podemos implementar la siguiente funcin de inicializacin del USART. Las dos primeras lneas establecen el Baud rate. Ese tema lo tratamos ms adelante por ser tan extenso como importante. De hecho, si hubiera que cambiar algo en esta funcin, seran esas dos lneas. El resto del cdigo ser casi siempre el mismo. //**************************************************************************** // Inicializa el USART a 9600-8N1. // Si BRGH = 1 => BaudRate = Fosc/(16*(SPBRG+1)) // Si BRGH = 0 => BaudRate = Fosc/(64*(SPBRG+1)) //**************************************************************************** void usart_init(void) { // Establecer el Baud rate a 9600. TXSTA.BRGH = 1; // Set high speed => ... SPBRG = 25; // SPBRG = Fosc/(BaudRate*16) -1 // Establecer modo asncrono, habilitar transferencias de datos,

CURSO_MICROS

P g i n a | 200

// configurar pines de E/S respectivos. TXSTA.SYNC = 0; // Seleccionar modo asncrono RCSTA.SPEN = 1; // Habilitar el puerto serial TXSTA.TXEN = 1; // Habilitar las transmisiones RCSTA.CREN = 1; // Habiiitar las recepciones TXSTA.TX9 = 0; // Transmisiones de 8 bits RCSTA.RX9 = 0; // Recepciones de 8 bits } Lo curioso es que las cosas ms importantes de esta funcin se pueden resumir con la notacin 9600 8N1. Significa Baud rate = 9600, formato de datos = 8 bits, No (sin) bit de paridad y 1 bit de Stop. Deberemos recordar esos parmetros a la hora de configurar el software terminal del lado del ordenador. Transmisin de datos del USART El dato que el USART transmitir debe ser previamente cargado en el registro TXREG. Con esa accin la transmisin comienza de inmediato. Mientras el registro TXREG contenga algn dato, el bit TXIF (del regitro PIR1) valdr 0. Cuando TXREG est nuevamente dispuesto para acoger otro dato, TXIF se setear automticamente por hardware. Hay otros detalles de consideracin que podran ser tiles para implementar una funcin de transmisin de datos, como hablar del registro oculto TSR o del bit TMRT, que indica si dicho registro est vaco o no. Con lo expuesto ya se puede codificar la siguiente funcin putch: //**************************************************************************** // Transmite el byte data por el USART //**************************************************************************** void putch(char data) { while(PIR1.TXIF == 0) // Esperar a que RXREG est vaco continue; // TXREG = data; // Poner dato a enviar } Dije que no detallara sobre el registro oculto TST pero tampoco nos vamos a perder de su utilidad. Con l se puede implementar la siguiente funcin de transmisin alternativa, que, en general, ser algo ms lenta que la anterior, aunque acaso tambin un poco ms segura. //**************************************************************************** // Transmite el byte data por el USART //**************************************************************************** void putch(char data) { while(TXSTA.TMRT == 0) // Esperar a que TSR est vaco continue; // TXREG = data; // Poner dato a enviar }

CURSO_MICROS

P g i n a | 201

Recepcin de datos del USART Los datos (de 8 9 bits) que llegan al USART se almacenan en el registro RCREG, que en realidad es un buffer de dos niveles, es decir, puede contener hasta dos datos. Apenas haya un dato en RCREG, se activar el flag RCIF (del registro PIR1). El bit RCIF es de solo lectura y se limpiar automticamente cuando hayamos terminado de leer todos los datos del buffer RCREG. Es recomendable que los datos que lleguen al USART se lean de inmediato. De no ser as, el buffer de RCREG se podra llenar y el USART podra atorarse o quedarse colgado. No debemos olvidar esto porque el procedimiento para desbloquear el USART es algo engorroso como para describirlo aqu y porque personalmente he visto que no siempre funciona. Asumiendo que seguiremos la recomendacin mencionada la funcin de recepcin de datos sera as: //**************************************************************************** // Recibe un byte de dato del USART //**************************************************************************** char getch(void) { while(PIR1.RCIF == 1) // Esperar a que haya algn dato en buffer RCREG continue; // return RCREG; // Retornar dato recibido } Clculo del Baud Rate Como habamos estudiado, el baud rate es la velocidad de transferencias de datos, se mide en bits/segundo y hay valores estndar que debemos respetar (4800, 9600, 19200, 115200, etc.). En el USART del PIC el baud rate depende de Fosc (frecuencia del XTAL en los PIC16F), del registro SPBRG y del bit BRGH segn alguna de las siguientes frmulas: BRGH = 1 (Alta velocidad) BRGH = 0 (Baja velocidad)

La pregunta es cundo usar una u otra frmula, cundo debemos poner BRGH a 1 a 0? Lamentablemente tendremos que probar las dos frmulas y ver con cul conseguimos mejores resultados. Por ejemplo, calculemos con el valor de 9600 para descubrir por qu es uno de los ms recurrentes y populares. Primero evaluemos la frmula poniendo BRGH = 1: (Se considera XTAL de 4MHz.)

CURSO_MICROS

P g i n a | 202

Resolvindola tenemos SPBRG = 25.04. Obviamente debemos truncar la parte decimal y quedamos con 25. Para saber el valor real de Baud rate que esto nos dar, volvemos a evaluar la frmula ahora con SPBRG = 25.

Vemos que no da los 9600 que buscbamos. La diferencia generar un error de (9615.38 - 9600)/9600 = 0.16%. Nada mal, verdad? Ahora veamos qu tenemos si ponemos BRGH = 0:

Al resolver la ecuacin tenemos SPBRG = 5.51 = 5. Para saber el valor real de Baud rate que esto nos dar, volvemos a evaluar la frmula ahora con SPBRG = 5.

sta es una gran diferencia y origina un error de (10416.67 9600)/9600 = 8.51%. Dado que la segunda desviacin es intolerable, concluimos en que deberemos quedarnos con los datos del primer clculo, con BRGH = 1, SPBRG = 25 y el error de 0.16%. Aunque algunos compiladores pueden calcular automticamente el valor de los registros para generar el baud rate que le indiquemos, no avisan error producido. Por lo tanto, siempre ser recomendable ver por nosotros mismos si el error es aceptable para nuestras aplicaciones. Si es una cuestin crucial, a veces tambin se puede optar por cambiar de XTAL; por ejemplo, uno de 3.6884 MHz (o sus mltiplos) deriva en errores de 0.00% para casi todos los baud rates. Los registros TXSTA y RCSTA en detalle. Transmit Status Register y Receive Status Register. No te dejes llevar del todo por sus nombres. Hay algunos bits de un mdulo que afectan al otro. ste es el registro TXSTA: CSRC TX9 TXEN Bit 7 CSRC: Clock Source Select bit Vlido solo en modo Sncrono: 1 = Modo maestro (reloj generado internamente desde BRG) SYNC --- BRGH TRMT TX9D Bit 0

CURSO_MICROS

P g i n a | 203

CSRC TX9 TXEN SYNC --- BRGH TRMT TX9D 0 = Modo esclavo (reloj proveniente de fuente externa) TX9: 9-bit Transmit Enable bit 1 = Selecciona transmisin de 9 bits 0 = Selecciona transmisin de 8 bits TXEN: TMR0 Clock Source Select bit 1 = Transmisiones habilitadas 0 = Transmisines inhabilitadas

SYNC: USART Mode Select bit 1 = Modo Sncrono 0 = Modo Asncrono BRGH:. High Baud Rate Select bit Vlido solo en modo Asncrono: 1 = Baud rate de Alta velocidad 0 = Baud rate de Baja velocidad TRMT: Transmit Shift Register Status 1 = Registro TSR vaco 0 = Registro TSR lleno TX9D: 9th bit of Transmit Data. Can be Parity bit

ste es el registro RCSTA: SPEN RX9 SREN Bit 7 SPEN: Serial Port Enable bit 1 = Puerto serial habilitado (configura los pines TX y RX para el puerto serial) CREN ADDEN FERR OERR RX9D Bit 0

CURSO_MICROS

P g i n a | 204

SPEN RX9 SREN CREN ADDEN 0 = Puerto serial inhabilitado RX9: 9-bit Receive Enable bit 1 = Selecciona recepciones de 9 bits 0 = Selecciona recepciones de 8 bits SREN: Single Receive Enable bit Vlido solo en modo Sncrono - maestro: 1 = Habilita recepcin nica 0 = Inhabilita recepcin nica CREN: Continues Receive Enable bit En modo Asncrono: 1 = Habilita recepcin continua 0 = Inhabilita recepcin continua En modo Sncrono:

FERR

OERR

RX9D

1 = Habilita recepcin continua hasta que el bit CREN se limpie. (CREN se sobrepone a SREN) 0 = Inhabilita recepcin continua ADDEN: Address Detect Enable bit Vlido solo en modo Asncrono de 9 bits (RX9 = 1): 1 = Habilita deteccin de direccin 0 = Inhabilita deteccin de direccin FERR: Framing Error bit 1 = Ocurri un error de frame 0 = No ocurri un error de frame OERR: Overrrun Error 1 = Ocurri un error de overrun (Se puede limpiar limpiando CREN) 0 = No ocurri un error de overrun RX9D: 9th bit of Receive Data. Can be Parity bit

CURSO_MICROS

P g i n a | 205

Prctica 1: Comunicacin PC PIC LCD ste es un programa clich en los ejemplos de interface entre un microcontrolador y un ordenador mediante el puerto serie. El programa terminal enva por el puerto serie las letras que presionemos en el teclado. El PIC los recibir, los reflejar al PC y tambin los visualizar en el display LCD. Haremos que un par de teclas generen instrucciones especiales: La tecla Escape, de cdigo 27, sirve pare limpiar la pantalla del LCD. La tecla Retroceso o Backspace, de cdigo 0x08 = \b, lleva el cursor del LCD una posicin atrs.

El circuito En ste como en el resto de los circuitos se emplea el transceiver DS275 por ser pequeo y prctico. De no encontrarlo, puedes usar el MAX232 (tienda de electrnica donde no haya este IC debera dejar de llamarse as). En ese caso el circuito a armar sera como el de la prxima prctica. Tambin puedes ver la seccin Interface serial microcontrolador-ordenador para mayor referencia.

CURSO_MICROS

P g i n a | 206

Los cdigos fuente En total tenemos cinco archivos: el principal, pc2lcd.c, que se capta con un simple golpe de vista, los archivos del USART (usart.h y usart.c) y los del LCD (lcd.h y lcd.c). ////////////////////////////////////////////////////////////////////////////// // File Name: pc2lcd.c // Processor: PIC16F87xA // Compiler: BoostC // Purpose: Acceso a LCD desde PC mediante PIC ////////////////////////////////////////////////////////////////////////////// #include <system.h> #include lcd.h #include usart.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 void main(void) { lcd_init(); // Ver interface en lcd.h lcd_cmd(LCD_CURBLK); // Mostrar Cursor + Blink usart_init(); // 9600 - 8N1 puts(\n\r Acceso a LCD desde PC mediante PIC \n\r); puts(\n\r Escribe en el LCD... \n\r); while(1) { if(kbhit()) // Si hay algn dato en el buffer de recepcin { char c = getch(); // Obtener un dato putch; // Devolver dato switch { case 0x1B: lcd_clear(); // Limpiar LCD break; case 0x08: lcd_cmd(0x10); // Cursor atrs lcd_data( ); // Escribir espacio blanco lcd_cmd(0x10); // Cursor atrs break; default: lcd_data; // Escribir c en LCD } } // Algunas otras tareas... nop(); } } ////////////////////////////////////////////////////////////////////////////// // File Name: usart.h // Target: PICmicros con USART // Compiler: BoostC // Author: Shawn Johnson

CURSO_MICROS

P g i n a | 207

////////////////////////////////////////////////////////////////////////////// #include <system.h> //**************************************************************************** // Congiguracin del Baud rate //**************************************************************************** #define XTALRate 4000000 // Frecuencia del XTAL (en Hz) #define BaudRate 9600 // Baud rate //**************************************************************************** #define putc putch // Equivalencias #define getc getch // Equivalencias // Usar kbhit para ver si hay algn dato en el buffer de recepcin antes de // llamar directamente a la funcin getch para evitar esperas innecesarias. #define kbhit() pir1.RCIF //**************************************************************************** // Prototipos de funciones //**************************************************************************** void usart_init(void); // Inicializa el USART char getch(void); // Lee un carcter void putch(char data); // Enva un carcter void puts(rom char * s); // Enva una cadena RAM terminada en NULL void puts(char * s); // Enva una cadena ROM terminada en NULL ////////////////////////////////////////////////////////////////////////////// // File Name: usart.c // Target: PICmicros con USART // Compiler: BoostC // Author: Shawn Johnson // // Description: Implementa funciones del USART para comunicaciones RS-232 ////////////////////////////////////////////////////////////////////////////// #include usart.h //**************************************************************************** // Implementacin de las funciones //**************************************************************************** //**************************************************************************** // Inicializa el USART. // Si BRGH = 1 => BaudRate = Fosc/(16*(SPBRG+1)) // Si BRGH = 0 => BaudRate = Fosc/(64*(SPBRG+1)) //**************************************************************************** void usart_init(void) { // Establecer el Baud rate. txsta.BRGH = 1; // Alta velocidad => SPBRG = Fosc/(BaudRate*16) - 1 spbrg = XTALRate/(BaudRate*16) - 1; // Establecer modo asncrono, habilitar transferencias de datos, // configurar pines de E/S respectivos.

CURSO_MICROS

P g i n a | 208

txsta.SYNC = 0; // Seleccionar modo asncrono rcsta.SPEN = 1; // Habilitar el puerto serial txsta.TXEN = 1; // Habilitar las transmisiones rcsta.CREN = 1; // Habiiitar las recepciones txsta.TX9 = 0; // Transmisiones de 8 bits rcsta.RX9 = 0; // Recepciones de 8 bits } //**************************************************************************** // Recibe un byte de dato del USART //**************************************************************************** char getch(void) { while(pir1.RCIF == 0) // Esperar a que haya algn dato en buffer RCREG continue; // return rcreg; // Retornar dato recibido } //**************************************************************************** // Transmite el byte data por el USART //**************************************************************************** void putch(char data) { while(pir1.TXIF == 0) // Esperar a que RXREG est vaco continue; // txreg = data; // Poner dato a enviar } /*void putch(char data) // Funcin putch alternativa { while(txsta.TMRT == 0) // Esperar a que TSR est vaco continue; // txreg = data; // Poner dato a enviar }*/ //**************************************************************************** // Envan cadenas ROM y RAM terminadas en NULL al terminal del USART //**************************************************************************** void puts(rom char * s) { char c; unsigned char i=0; while(c = s[i++]) putch; } void puts(char * s) { char c; while(c = *s++) putch; }

CURSO_MICROS

P g i n a | 209

Descripcin de los cdigos del programa En el lenguaje C de los ordenadores kbhit es una funcin que sirve para ver si hay algn dato en el buffer del teclado, o sea, para saber si alguien presion una tecla. El microcontrolador no tendr teclado pero ya que de alguna forma se conecta con uno mediante el puerto serie del ordenador, se suele usar ese nombre con un propsito similar. Siempre ser recomendable usar kbhit antes de leer el USART con getch porque segn su implementacin es de las llamadas funciones de bloqueo. O sea, si usramos getch directamente el programa podra quedarse esperando hasta que llegue algn dato, perdiendo en vano el tiempo que podra emplearse para ejecutar otras rutinas, como el nop de Algunas otras tareas... Eso fue solo un bosquejo, por supuesto. Por otro lado, si Algunas otras tareas... tomara mucho tiempo para su ejecucin, podran llegar datos al USART que si no los leemos a tiempo podran atorarlo. Ser muy raro que pase eso porque el ordenador no suele enviar datos muy seguidos, aparte de que podamos teclear con velocidad. De todos modos, para conjurar el riesgo la mejor forma de recibir datos por el USART ser usando las interrupciones. Ahora pasemos a ver los archivos de la librera del USART. Es la librera que vamos a usar en adelante. Como siempre, es preferible dividirlo as, en dos partes, para su fcil manejo. Por ejemplo, si deseamos cambiar el baud rate, sabemos que solo debemos editar el pequeo archivo de cabecera usart.h. Debo aclarar que mi funcin usart_init utiliza la frmula de baud rate para alta velocidad ya que ser la ms adecuada en la mayora de los casos. No estar de ms por tanto asegurarse de que se obtenga el baud rate adecuado. El programa Tera Term Configuracin del programa Smart Term Ya tenemos todo listo para el microcontrolador. Ahora nos falta ejecutar algn programa terminal en nuestro ordenador para empezar el intercambio de datos. De los muchos que hay vamos a elegir el Smart Term, que es de licencia GPL, o sea que lo puedes bajar libremente desde la web http://www.smarterm.com. Formato de numeros Prctica 2: Tratamiendo de textos y nmeros Los caracteres de las consolas de los programas terminales usados en estas aplicaciones son ASCII (puro texto valga decir). Para enviar datos que el microcontrolador debe interpretar en otros formatos, por ejemplo como nmeros, es necesario hacer una conversin. En este programa enviamos al PIC un nmero en decimal o hexadecimal desde la consola del programa terminal. El PIC almacenar los caracteres en un buffer en forma de texto y tras convertirlo en nmero hexadecimal o decimal lo visualizar en el terminal.

CURSO_MICROS

P g i n a | 210

El circuito En principio, el circuito para esta prctica es el mismo que el de la anterior. En esta ocasin se sustituye el DS275 por el MAX232 como ejemplo de su uso, en caso de no contar con el DS275 o de ser necesario un flujo de datos full duplex.

El cdigo fuente

/////////////////////////////////////////////////////////////////////////////// // File Name: parser.c // Processor: PIC16F87xA // Compiler: BoostC // Purpose: Conversin de texto numrico en nmero y viceversa /////////////////////////////////////////////////////////////////////////////// #include <system.h> #include <ctype.h> // Contiene funciones ixdigit, isdigit, tolower,... #include <stdlib.h> // Contiene funciones itoa, atoi,... #include usart.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 //**************************************************************************** // Lee una cadena de texto de len caracteres filtrados por fptr. // El tamao de buffer debe ser mayor que len. //**************************************************************************** void ReadStr(char (*fptr)(char), char * buffer, unsigned char len) {

CURSO_MICROS

P g i n a | 211

char c; unsigned char i=0; while(1) { // Bucle incondicional c = getc(); if(fptr&&(i<len)) // Si c es ... { buffer[i++] = c; // Guardar en buffer putc; // Eco } else if((c==\b)&&(i)) // Si c es RETROCESO y si i>0 { i--; // putc; // Eco } else if((c==\r)&&(i)) // Si c es ENTER y si i>0 { buffer[i] = \0; // Poner un 0x00 (fin de cadena) putc; // Eco break; // Salir del bucle } } } /* Funcin principal ********************************************************/ void main() { char buff[10]; char op; unsigned int n; usart_init(); // 9600 - 8N1 puts(\n\r Ingreso de nmeros desde teclado de PC \n\r); puts(\n\r Escoja un sistema: ); puts(\n\r [D]ecimal o [H]exadecimal \n\r); while(1) { // Bucle incondicional op = getc(); op = tolower(op); // Convertir op en minscula if((op==h)||(op==d)) { // Si op es h o d putc(op); // Reflejar al terminal break; // Y salir del bucle } } while(1) { if(op==d) { puts(\n\r\r Ingrese un decimal de 4 dgitos: ); ReadStr(isdigit, buff, 4); // Leer 4 dgitos decimales en buff n = atoui_dec(buff); // Convertir texto decimal en nmero puts( Hexadecimal = 0x); uitoa_hex(buff, n, 4); // Convertir n en texto hexa de 4 dgitos puts(buff); // Mostrar buff

CURSO_MICROS

P g i n a | 212

} else { puts(\n\r\r Ingrese un hexadecimal de 4 dgitos: ); ReadStr(isxdigit, buff, 4); // Leer 4 dgitos hexadecimales en buff n = atoui_hex(buff); // Convertir texto hexadecimal en nmero puts( Decimal = ); uitoa_dec(buff, n, 5); // Convertir n en texto dec de 5 dgitos puts(buff); // Mostrar buff } } } Descripcin del programa Antes de nada quiero aclarar que pese a mi propia recomendacin de usar primero kbhit antes de llamar a getc, no lo hice porque aparte de convertir datos este programa no tiene otra cosa que hacer mas que leer el USART. As que le di permiso para quedarse es eso todo el tiempo que quiera. En este programa se utilizan algunas funciones como tolower, isdigit e isxdigit. Ellas y varias ms se hallan en la librera ctype.h del lenguaje C estndar. Puedes abrir dicho archivo para ver sus descripciones. La funcin ReadStr se encarga de leer una cadena de texto del terminal, filtrando solo los caracteres numricos y los cdigos de Retroceso (\b = 0x08) y Retorno de Carro (\r = 0x0D). De esa forma adems se trata de evitar la recepcin de posibles caracteres errneos. ReadStr tambin establece un lmite para la cadena. void ReadStr(char (*fptr)(char), char * buffer, unsigned char len) { // ... if(fptr&&(i<len)) // Si c es ... // ... } No es nada complicado de entender salvo quiz por el primer parmetro que recibe, fptr. Se trata de un puntero a funcin, un mtodo que raramente se emplea porque siempre es posible encontrar formas alternativas. Funciona as: cuando se hace la llamada ReadStr(isdigit, buff, 4); // Leer 4 dgitos decimales en buff fptr es equivalente a la ejecucin de isdigit. Y cuando le pasemos la funcin isxdigit a ReadStr, fptr ser equivalente a isxdigit. Tras obtener la cadena en formato de texto decimal o hexadecimal, queda por convertirla en nmero y luego al revs, segn sea el caso. Las funciones incluidas en el C estndar para estas tareas son atoi e itoa. Adems de ellas el compilador BoostC incluye algunas funciones ligeras como atoui_dec, uitoa_hex, atoui_hex y uitoa_dec, las cuales prefer utilizar en para ahorrar un

CURSO_MICROS

P g i n a | 213

poco de memoria. Para ms informacin al respecto puedes examinar el archivo stdlib.h o recurrir al manual del compilador. Termino comentando que es comprensible el lmite de 4 dgitos impuesto en el ingreso de los nmeros hexadecimales ya que la variable n es un unsigned int. As se puede abarcar todo su rango: desde 0x0000 hasta 0xFFFF. Sin embargo, aunque el rango de una variable unsigned int en decimal es de 0 a 65535, he puesto el lmite de 9999 (nmero mayor de 4 dgitos decimales) para no poner cdigos de comprobacin que extendiendan demasiado el cdigo. Las Interrupciones del USART El USART puede generar dos interrupciones: una cuando recibe datos y otra cuando transmite datos. Eso para decirlo simple. Espero que recuerdes los mecanismos descritos en las secciones Transmisin de datos del USART y Recepcin de datos del USART o puedes regresar a revisarlas para entender mejor lo que sigue. INTCON GIE PIE1 PIR1 PSPIE PSPIF PEIE ADIE ADIF TMR0IE INTE RCIE RCIF TXIE TXIF RBIF TMR1I SSPIE CCP1IE TMR2IE E SSPIF CCP1IF TMR2IF TMR1IF RBIE TMR0IF INTF

La Interrupcin de Recepcin se habilita seteando los bits RCIE, PEIE y GIE. El flag de esta interrupcin RCIF se setear cuando haya algn nuevo dato en el buffer de recepcin RCREG y disparar su interrupcin si ella est habilitada. RCIF es de solo lectura y se limpia automticamente por hardware despus de leer todo el buffer RCREG. La Interrupcin de Transmisin se habilita seteando los bits TXIE, PEIE y GIE. El flag que la idendifica TXIF se activar cuando el registro de transmisin TXREG est vaco, esto es, cuando est listo para cargar el primer (o siguiente) dato a transmitir y disparar su interrupcin si ella est habilitada.. El bit TXIF tambin es de solo lectura y se limpia al escribir un nuevo dato en TXREG o inhabilitando su interrupcin. Prctica 3: Interrupciones del USART Cada dato que llegue al USART es vuelto a enviar al terminal serial. Por otro lado, se enva todo un buffer (una cadena de texto) pero usando interrupciones.

CURSO_MICROS

P g i n a | 214

El circuito El mismo circuito de la prctica anterior.

El cdigo fuente

/////////////////////////////////////////////////////////////////////////////// // File Name: interrupts.c // Processor: PIC16F87xA // Compiler: BoostC // Author: Shawn Johnson // Purpose: Uso de las interrupciones del USART /////////////////////////////////////////////////////////////////////////////// #include <system.h> #include usart.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 volatile char buf[] = \n\r Uso de interrupciones del USART \n\r\r; void SendNext(void); //**************************************************************************** // ISR o Gestor de interrupciones (del USART). // La interrupcin de transmisin se dispara cuando se pueda enviar un dato. // La interrupcin de recepcin se dispara cuando haya llegado algn dato. // Los flags RCIF y TXIF son de solo lectura y pueden limpiarse solos. //**************************************************************************** void interrupt(void)

CURSO_MICROS

P g i n a | 215

{ if (pir1.RCIF && pie1.RCIE) // Interrupcin de recepcin ? { char c = getc(); // Leer dato putc; // Devolver dato } if (pir1.TXIF && pie1.TXIE) // Interrupcin de transmisin ? { SendNext(); } } // Funcin principal ********************************************************* void main(void) { usart_init(); // 9600 - 8N1 pie1.RCIE = 1; pie1.TXIE = 1; intcon.PEIE = 1; intcon.GIE = 1; // Habilitar interrupciones de recepcin // Habilitar interrupciones de transmisin // Habilitacin de int de perifricos // Habilitacin global de ints

while(1) { /* Aunque no haya nada ms que hacer aqu, no se puede entrar en modo Sleep porque el circuito del USART se detendra */ nop(); } } //**************************************************************************** // Deposita en el registro de transmisin el siguiente dato a enviar. // Si no hay ms datos que enviar, inhabilita la interrupcin respectiva. //**************************************************************************** void SendNext(void) { static unsigned char i = 0; char c = buf[i]; if(c != 0) // Fin de buffer ? { putc; i++; } else pie1.TXIE = 0; // Inhabilitar interrupciones de transmisin } Descripcin del programa Observa que en la funcin principal main no se transfiere ningn dato. Todos viajan por interrupciones. Primero anlicemos la interrupcin de recepcin, que es la que se usa con ms frecuencia y adems est fcil de captar.

CURSO_MICROS

P g i n a | 216

if (pir1.RCIF && pie1.RCIE) // Interrupcin de recepcin ? { c = getc(); // Leer dato putc; // Devolver dato } Eso es todo: cuando haya algn dato llegado lo recibimos y lo devolvemos :) La interrupcin de transmisin no es tan usual como la anterior. Es una tcnica sofisticada y ms eficiente pero que raras veces resulta realmente necesaria. En el programa funciona as: una vez habilitada, la interrupcin se disparar de inmediato ya que el registro de transmisin estar vaco. As se empieza a enviar el primer dato de buf. La interrupcin se volver a disparar dada vez que el USART termine de enviar el dato anterior y solo parar cuando la inhabilitemos. Fin de la historia. El buffer circular o buffer de anillo Recordemos que no podemos depositar un dato en el registro de transmisin del USART hasta que se termine de enviar el dato anterior. Mientras se espera a que eso pase el CPU podra perder tiempo valioso. La solucin es guardar los datos en un buffer y que se vayan transmitiendo a su momento utilizando interrupciones, parecido a lo que se vio en la prctica pasada. Por otro lado, el registro de recepcin, al ser un buffer de dos niveles, s puede recibir un segundo dato antes de que se haya ledo el dato previo. Pero si siguen llegando ms datos sin que sean recogidos, no solo se perderan, sino que bloquearan el USART. Incluso si los datos se leyeran a tiempo utilizando interrupciones, qu se hara con ellos si el CPU an est ocupado procesando los datos previos? Tienes razn! Podran ir guardndose en un buffer. Pues bien, en ambos casos las cosas saldrn mejor si se usa un buffer de tipo circular. Un buffer circular es un recurso de programacin tan viejo como til en el intercambio de todo tipo de datos, no solo en comunicaciones del USART. No es ms que un buffer o array ordinario que adopta su nombre por la forma en que se ponen y sacan sus elementos. Un buffer circular trabaja bsicamente con dos ndices, que aqu llamaremos Inpointer y Outpointer. No son como los punteros que define el lenguaje C, son simples variables que operan como ndices para acceder a los elementos del buffer. Ambos ndices tienen avance incremental y cclico, es decir, se incrementan de uno en uno y luego de apuntar al ltimo elemento del buffer vuelven a apuntar al primero. Eso explica su nombre.

Estructura de un buffer circular de N elementos.

CURSO_MICROS

P g i n a | 217

Al inicio los dos ndices apuntan al primer elemento del buffer. La pregunta es cundo y cmo se incrementan? Cada nuevo dato a guardar en el buffer ser depositado en la casilla actualmente apuntada por Inpointer. A continuacin Inpointer se incrementa en 1. (Inpointer para datos In = entrada.) Por otro lado, cada dato que salga del buffer ser el de la casilla actualmente apuntada por Outpointer. A continuacin Outpointer se incrementa en 1. (Outpointer para datos Out = salida.)

Con todo lo expuesto ya puedes ensayar cmo funciona el buffer circular. Descubrirs que tiene un comportamiento FIFO: los primeros datos en entrar sern los primeros en salir; que en tanto haya espacio en el buffer siempre se podrn meter ms datos sin importar en qu posiciones vayan, evitando el riesgo de sobreescribir posiciones ya ocupadas. Podras hacer eso con un buffer lineal? Muy difcil, verdad? Ahora pasemos de los ensayos a la prctica real. Para saber si en el buffer hay espacio para meter ms datos o si hay al menos un dato que sacar, se debe usar la diferencia entre las posiciones de los punteros. Por lo confuso que se ve eso, es preferible emplear una variable adicional que se incremente con cada dato ingresado y se decremente con cada dato extrado. Prctica 4: Buffer circular con Interrupciones El uso de un buffer circular en las recepciones de datos es una tcnica robusta que se convierte en una necesidad de facto por razones ya explicadas. En las transmisiones, en cambio, brinda una eficiencia superflua y hasta innecesaria, salvo que la aplicacin realmente la requiera. No quiero poner programas de esos aqu porque son demasiado grandes como ejemplos. Superficialmente esta prctica se ve igual que la primera de este captulo. Solo que ahora todos los datos son transferidos por interrupciones pasando por buffers circulares. El circuito El circuito es el mismo de la primera prctica.

CURSO_MICROS

P g i n a | 218

El cdigo fuente

/////////////////////////////////////////////////////////////////////////////// // File Name: RingBuffer.c // Processor: PIC16F87xA // Compiler: BoostC // Author: Shawn Johnson // Purpose: Uso de buffers circulares con las interrupciones del USART /////////////////////////////////////////////////////////////////////////////// #include <system.h> #include lcd.h #include usart.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 char GetFromTXBuffer(void); char GetFromRXBuffer(void); void PutToTXBuffer(char data); void PutToRXBuffer(char data); #define TXBufferSize 50 // Tamao de buffer circular de Tx #define RXBufferSize 50 // Tamao de buffer circular de Rx volatile char TXBuffer[TXBufferSize]; // Buffer circular de Tx volatile char RXBuffer[TXBufferSize]; // Buffer circular de Rx volatile unsigned char TXInpointer = 0;

CURSO_MICROS

P g i n a | 219

volatile unsigned char RXInpointer = 0; volatile unsigned char TXOutpointer = 0; volatile unsigned char RXOutpointer = 0; volatile unsigned char TXBufferData = 0; volatile unsigned char RXBufferData = 0; volatile unsigned char TXBufferSpace = TXBufferSize; volatile unsigned char RXBufferSpace = RXBufferSize; //**************************************************************************** // ISR o Gestor de interrupciones (del USART) //**************************************************************************** void interrupt(void) { char c; if (pir1.RCIF && pie1.RCIE) // Receive interrupt ? { c = getc(); // Leer dato if(RXBufferSpace) // Si hay espacio en RXBuffer PutToRXBuffer; else // RXBuffer est lleno nop(); // Cdigo para TXBuffer lleno } if (pir1.TXIF && pie1.TXIE) // Transmit interrupt ? { if(TXBufferData) // Si hay datos en TXBuffer { c = GetFromTXBuffer(); // Estraer dato putc; // Enviarlo } else // Ya no hay datos en TXBuffer pie1.TXIE = 0; // Inhabilitar esta interrupcin } } //**************************************************************************** // Funcin principal //**************************************************************************** void main(void) { char c; unsigned char i=0; rom char * text = \n\r Escribe en el LCD... \n\r; usart_init(); // 9600 - 8N1

lcd_init(); // Ver interface en lcd.h lcd_cmd(LCD_CURBLK); // Mostrar Cursor + Blink while(c = text[i++]) // Cargar TXBuffer { if(TXBufferSpace) // Si hay espacio en TXBuffer PutToTXBuffer; // Meter c else nop(); // Cdigo para TXBuffer lleno

CURSO_MICROS

P g i n a | 220

} pie1 = (1<<RCIE)|(1<<TXIE); intcon = (1<<GIE)|(1<<PEIE); // Habilitar interrupciones del // USART

while(1) { if(RXBufferData) // Si hay datos en RXbuffer { c = GetFromRXBuffer(); // Obtener un dato switch { case 0x1B: lcd_clear(); // Limpiar LCD break; case 0x08: lcd_cmd(0x10); // Cursor atrs lcd_data( ); // Escribir espacio blanco lcd_cmd(0x10); // Cursor atrs break; default: lcd_data; // Escribir c } } // Algunas otras tareas... nop(); } } //**************************************************************************** // Extrae un dato de TXBuffer. // Antes de llamar se debe comprobar si hay algn dato con if(TXBufferData) //**************************************************************************** char GetFromTXBuffer(void) { char c = TXBuffer[TXOutpointer]; // Extraer dato if(++TXOutpointer >= TXBufferSize) // Al pasar el lmite TXOutpointer = 0; // Dar la vuelta TXBufferData--; // Un dato menos TXBufferSpace++; // Un espacio ms return c; // } //**************************************************************************** // Extrae un dato de RXBuffer. // Antes de llamar se debe comprobar si hay algn dato con if(RXBufferData) //**************************************************************************** char GetFromRXBuffer(void) { char c = RXBuffer[RXOutpointer]; // Extraer dato if(++RXOutpointer >= RXBufferSize) // Al pasar el lmite RXOutpointer = 0; // Dar la vuelta RXBufferData--; // Un dato menos RXBufferSpace++; // Un espacio ms return c; // }

CURSO_MICROS

P g i n a | 221

//**************************************************************************** // Ingresa un dato en TXBuffer // Antes de llamar se debe comprobar si hay espacio con if(TXBufferSpace) //**************************************************************************** void PutToTXBuffer(char data) { TXBuffer[TXInpointer] = data; // Ingresar dato if(++TXInpointer >= TXBufferSize) // Al pasar el lmite TXInpointer = 0; // Dar la vuelta TXBufferData++; // Un dato ms TXBufferSpace--; // Un espacio menos pie1.TXIE = 1; // Habilitar inter de transmisin } //**************************************************************************** // Ingresa un dato en RXBuffer // Antes de llamar se debe comprobar si hay espacio con if(RXBufferSpace) //**************************************************************************** void PutToRXBuffer(char data) { RXBuffer[RXInpointer] = data; // Ingresar dato if(++RXInpointer >= RXBufferSize) // Al pasar pasar el lmite RXInpointer = 0; // Dar la vuelta RXBufferData++; // Un dato ms RXBufferSpace--; // Un espacio menos } Descripcin del programa Como ves, hay dos buffers circulares, uno para transmisiones y otro para recepciones. Una vez implementados su uso es bastante simple. Solo compara esto: para enviar y recibir datos en un programa rstico se utilizan funciones como putc para depositar un dato en el registro de transmisin, y getc para leer del minibuffer de recepcin de 2 datos. En cambio, con los buffers circulares podemos usar las funciones como PutToTXBuffer o GetFromRXBuffer para depositar/leer en/de sus megabuffers de transmisin y recepcin. Se usan las variables como RXBufferData o TXBufferSpace para comprobar si hay datos o espacios para ellos en los buffers. Prctica 5: Interface RS-232 entre dos PICs Si se puede conectar un PIC a un ordenador, dime si no se podr conectar a otro PIC. Es tan burdo que en vez de usar con PICs con USART esta prctica la haremos con dos PIC16F84A e implementando funciones RS-232 software. Conectar dos microcontroladores puede ser til cuando uno solo no basta ya sea quiz por la falta de algunos pines o porque se le quiere dar al segundo microcontrolador una tarea dedicada o exclusiva para que la desarrolle con la mxima eficiencia posible, o por una combinacin de ambas razones, como en esta prctica.

CURSO_MICROS

P g i n a | 222

Los LCDs seriales son muy atractivos por su fcil conexin a un microcontrolador. Los hay con interface RS-232, I2C o SPI. Con solo buscar en Google vers la enorme cantidad de modelos disponibles, aunque con precios que pueden desalentar a muchos. Una idea es tomar un LCD paralelo cualquiera y convertirlo en serial. Aunque el tamao de su circuito resultante incomode un poco, su coste puede ser muy inferior sobre todo si el diseo ser final, donde se podra escoger un PIC con los recursos mnimamente necesarios. El circuito Como el PIC del circuito remoto tendr siempre la nica tarea de controlar directamente el LCD, no se escatiman los pines de interface. As que operaremos el LCD en modo de 8 bits y comprobando el bit de Busy Flag para lograr su mejor performance.

Los cdigos fuente Los archivos de cada PICmicro son main.c para uno y rs232lcd.c para el otro. El tamao ms pequeo del cdigo de main.c revela que se trata del primer PICmicro. Se supone que toda la carga de controlar el LCD en bajo nivel descanza sobre el segundo PIC. /////////////////////////////////////////////////////////////////////////////// // File Name: main.c // Processor: PIC16F84A // Compiler: BoostC // Author: Shawn Johnson // Purpose: Control de LCD serial rs232

CURSO_MICROS

P g i n a | 223

/////////////////////////////////////////////////////////////////////////////// #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 // Prototipos de funciones void lcd_init(void); void lcd_puts(rom char *); void lcd_clear(void); void lcd_gotorc(char, char); void putc(char c); // definiciones #define LCD_INIT 0x00 // Inicializar LCD (user command) #define LCD_CLEAR 0x01 // Limpiar Display #define LCD_LINE1 0x80 // Lnea 1 posicin 0 #define LCD_LINE2 0xC0 // Lnea 2 posicin 0 #define LCD_CURSOR 0x0E // Mostrar solo Cursor #define LCD_BLINK 0x0D // Mostrar solo Blink #define LCD_CURBLK 0x0F // Mostrar Cursor + Blink #define RS232_Tx porta.3 // Lnea de transmisin #define RS232_Txtris trisa.3 // #define putch putc void main(void) { RS232_Tx = 1; RS232_Txtris = 0; // Equivalencia

// Iniciar en estado spacing // Lnea de transmisin salida

delay_ms(5); // Para que el otro PIC complete su inicializacin lcd_init(); lcd_puts(Emulacion de LCD \n serial RS232 ); option_reg.NOT_RBPU = 0; // Habilitar pull-ups while(1) { if(portb.0==0) // Si botn de pin RB0 est pulsado... { lcd_init(); // Reinicializar el LCD while(portb.0==0); // Esperar botn libre } else if(portb.1==0) // Si botn de pin RB1 est pulsado... { lcd_clear(); lcd_puts(Programacion de \nmicros PIC y AVR); while(portb.1==0); } else if(portb.2==0) // Si botn de pin RB2 est pulsado... { lcd_clear(); lcd_puts(Web site: www.\ncursomicros.com); while(portb.2==0); }

CURSO_MICROS

P g i n a | 224

} } //**************************************************************************** // Inicializa el LCD //**************************************************************************** void lcd_init(void) { putc(0xFE); // Enviar prefijo de comando putc(LCD_INIT); // Enviar comando (personalizado) delay_ms(50); } //**************************************************************************** // Enva cadenas ROM terminadas en null al LCD. //**************************************************************************** void lcd_puts(rom char * s) { unsigned char c, i=0; while(c = s[i++]) { if(c==\n) lcd_gotorc(2,1); // Ir a lnea 2 else putc; // Escribir carcter } } //**************************************************************************** // Limpia el LCD y regresa el cursor a la primera posicin de la lnea 1. //**************************************************************************** void lcd_clear(void) { putc(0xFE); // Prefijo de comando putc(LCD_CLEAR); // Enviar instruccin Clear Display } //**************************************************************************** // Ubica el cursor del LCD en la columna c de la lnea r. //**************************************************************************** void lcd_gotorc(char r, char c) { if(r==1) r = LCD_LINE1; else r = LCD_LINE2; putc(0xFE); // Prefijo de comando putc(r+c-1); // Enviar instruccin Set DDRAM Address } //**************************************************************************** // Enva un carcter por el terminal RS232. Formato de frame = 9600 8N1 //**************************************************************************** void putc(char c) { unsigned char i; RS232_Tx = 0; // Enviar Bit de Start

CURSO_MICROS

P g i n a | 225

delay_us(84); // Periodo de Baud Rate for(i=0; i<8; i++) // Enviar 8 bits { if(c & 1) // Si bit 0 es 1... RS232_Tx = 1; // enviar 1. (102) if(!(c & 1)) // De otro modo... RS232_Tx = 0; // enviar 0. (104) c >>= 1; // Desplazar dato delay_us(80); // Periodo de Baud Rate } nop(); // Ajuste de periodo RS232_Tx = 1; // Enviar Bit de Stop delay_us(92); // Periodo de Baud Rate } A continuacin, el archivo del segundo PIC, rs232lcd.c. /////////////////////////////////////////////////////////////////////////////// // File Name: rs232lcd.c // Processor: PIC16F84A // Compiler: BoostC // Author: Shawn Johnson // Purpose: LCD serial rs232 + Buffer circular /////////////////////////////////////////////////////////////////////////////// #include <system.h> #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON #pragma CLOCK_FREQ 4000000 //**************************************************************************** // Configuracin de los pines de interface //**************************************************************************** #define lcd_DBUS portb // Data Bus (RB4-DB4,...,RB7-DB7) #define lcd_DBUStris trisb // #define lcd_E porta.2 // Pin Enable #define lcd_Etris trisa.2 // #define lcd_RW porta.1 // Pin Read/Write #define lcd_RWtris trisa.1 // #define lcd_RS porta.0 // Pin Register Select #define lcd_RStris trisa.0 // #define RS232_Rx porta.4 // Lnea de recepcin #define RS232_Rxtris trisa.4 // //**************************************************************************** // Prototipos de funcin //**************************************************************************** char GetFromBuffer(void); void PutToBuffer(char); char getc(void); void lcd_init(void); void lcd_cmd(char); void lcd_data(char);

CURSO_MICROS

P g i n a | 226

void lcd_write(char, char); char lcd_read(char); // Definiciones y Variables globales #define getch getc // Equivalencia #define LCD_INIT 0x00 // Comando personalizado para inicializar el LCD #define BufferSize 50 // Tamao del buffer circular // Variables globales volatile char Buffer[BufferSize]; // Buffer circular volatile unsigned char Inpointer = 0; volatile unsigned char Outpointer = 0; volatile unsigned char BufferData = 0; volatile unsigned char BufferSpace = BufferSize; //**************************************************************************** // ISR o Gestor de interrupcin (del Timer0) //**************************************************************************** void interrupt(void) { if (intcon.T0IF) { if(BufferSpace) // Si hay espacio en el buffer circular PutToBuffer(getc()); else nop(); // Cdigo para Buffer lleno tmr0 = 255; // Poner Timer0 a punto del desbordamiento intcon.T0IF = 0; // Limpiar flag } else intcon = (1<<T0IE); // Permitir solo interrupcin de Timer0 } //**************************************************************************** void main(void) { char c; char pf = 0; // Flag de prefijo de comando // ********************************************************************** // Configuracin de OPTION_REG, TMR0 e INTCON para que el Timer0 genere // una interrupcin ante un flanco de bajada en el pin RA4/T0CKI. // ********************************************************************** option_reg = 0b10111111; // OPTION_REG bitmap: // RBPU INTEDG T0CS T0SE PSA PS2 PS1 PS0 // * RBPU = 1 Inhabilitar Pull-ups // * INTEDG = No usamos intr RB0/INT // * TOCS = 1 Timer0 en modo Contador // * T0SE = 1 Flanco de bajada de pin RA4/T0CKI // * PSA = 1 Prescaler para Watchdog // * PS2:PS0 = No valen sin prescaler tmr0 = 255; // Timer0 a punto del desbordamiento intcon = (1<<T0IE)|(1<<GIE); // Habilitar interrupcin del Timer0

CURSO_MICROS

P g i n a | 227

while(1) { if(BufferData) // Si hay datos en Buffer { c = GetFromBuffer(); // Leer if((pf==0)&&(c==0xFE)) // { pf = 1; } else if(pf==1) { if(c==LCD_INIT) // Comando personalizado lcd_init(); else // Comando estndar lcd_cmd; pf = 0; } else { lcd_data; } } } } //**************************************************************************** // Extrae un dato de Buffer. // Antes de llamar se debe comprobar si hay algn dato con if(BufferData) //**************************************************************************** char GetFromBuffer(void) { char c = Buffer[Outpointer]; // Extraer dato if(++Outpointer >= BufferSize) // Al pasar el lmite Outpointer = 0; // Dar la vuelta BufferData--; // Un dato menos BufferSpace++; // Un espacio ms return c; // } //**************************************************************************** // Ingresa un dato en Buffer // Antes de llamar se debe comprobar si hay espacio con if(BufferSpace) //**************************************************************************** void PutToBuffer(char data) { Buffer[Inpointer] = data; // Ingresar dato if(++Inpointer >= BufferSize) // Al pasar pasar el lmite Inpointer = 0; // Dar la vuelta BufferData++; // Un dato ms BufferSpace--; // Un espacio menos } //**************************************************************************** // Ejecuta la inicializacin software completa del LCD. La configuracin es:

CURSO_MICROS

P g i n a | 228

// Interface de 8 bits, despliegue de 2 lneas y caracteres de 5x7 puntos. //**************************************************************************** void lcd_init(void) { lcd_E = 0; // Valores iniciales de E, RW y RS lcd_RS = 0; // lcd_RW = 0; // lcd_Etris = 0; // Direcciones de E, RW y RS lcd_RStris = 0; // lcd_RWtris = 0; // delay_ms(45); // > 40 ms lcd_write(0x30,0); // Function Set: 8-bit delay_ms(5); // > 4.1 ms lcd_write(0x30,0); // Function Set: 8-bit delay_ms(1); // > 100 s lcd_write(0x30,0); // Function Set: 8-bit delay_ms(1); // > 40 s lcd_write(0x38,0); // Function Set: 8-bit, 2lines, 47font lcd_cmd(0x0C); // Display ON/OFF Control: lcd_cmd(0x01); // Clear Display lcd_cmd(0x06); // Entry Mode Set } //**************************************************************************** // Envan instrucciones de comando y de datos al LCD. //**************************************************************************** void lcd_cmd(char cmd) { while(lcd_read(0)&0x80) // Mientras LCD ocupado continue; // esperar lcd_write(cmd, 0); } void lcd_data(char data) { while(lcd_read(0)&0x80) // Mientras LCD ocupado continue; // esperar lcd_write(data, 1); } //**************************************************************************** // Escribe una instruccin en el LCD: // Si RS = 0 la instruccin es de comando (Function Set, Entry Mode set, etc). // Si RS = 1 la instruccin es de dato y va a la DDRAM/CGRAM. // El LCD debe estar libre antes de llamar esta funcin. //**************************************************************************** void lcd_write(char data, char RS) { if(RS) lcd_RS = 1; // Escribir en DDRAM/CGRAM else lcd_RS = 0; // Escribir en Registro de Comandos nop(); nop(); nop(); // Permite actualizar Puntero de RAM nop(); nop(); lcd_RW = 0; // Modo Escritura lcd_DBUStris = 0x00; // Puerto para salida lcd_DBUS = data; // Colocar dato

CURSO_MICROS

P g i n a | 229

nop(); nop(); lcd_E = 1; nop(); nop(); lcd_E = 0; }

// tAS, set-up time > 140 ns // Pulso de Enable // Enable pulse width > 450 ns

//**************************************************************************** // Lee un byte de dato del LCD. // Si RS = 1 se lee la locacin de DDRAM/CGRAM. // Si RS = 0 se lee el bit de Busy Flag + el Puntero de RAM. //**************************************************************************** char lcd_read(char RS) { char data; if(RS) lcd_RS = 1; // Leer de DDRAM/CGRAM else lcd_RS = 0; // Leer Busy Flag + Puntero de RAM lcd_RW = 1; // Modo Lectura lcd_DBUStris = 0xFF; // Puerto para entrada nop(); nop(); // tAS, set-up time > 140 ns lcd_E = 1; // Habilitar LCD nop(); nop(); // Data Delay Time > 1320 ns data = lcd_DBUS; // Leer dato lcd_E = 0; // Inhabilitar LCD return data; // Juntar nibbles } //**************************************************************************** // Lee un carcter del terminal RS232. Formato de frame = 9600-8N1 //**************************************************************************** char getc(void) { unsigned char i, c=0; while(RS232_Rx); // Esperar el Bit de Start delay_us(113); // 1.5 periodos Baud rate 157-16-13 for(i=0; i<8; i++) // Leer 8 bits { c >>= 1; // Desplazar dato if(RS232_Rx) // Leer lnea RX c |= 0x80; // Setear bit si necesario delay_us(80); // Periodo Baud rate nop(); nop(); // Ajuste } return c; } Descripcin de los programas Empecemos por el programa del primer PIC, main.c. ste codigo es muy fcil de entender. Como sabemos, el LCD tiene dos tipos de instrucciones: las de comando y las de datos de caracteres. Los datos de caracteres se envan directamente con la funcin putc. Estos caracteres sern recibidos y por el otro PIC y los visualizar en la pantalla del LCD. Solo si el LCD

CURSO_MICROS

P g i n a | 230

tuviera activa la memoria CGRAM dichos datos iran all para crear caracteres personalizados. putc; // Escribir carcter

Por otro lado, los datos que se enven precedidos por el valor 0xFE sern entendidos y ejecutados por el otro PIC como instrucciones de comando, como Clear display, Set DDRAM Address, etc. Los cdigos de dichos comandos son los mismos que usa el controlador interno del LCD, salvo LCD_INIT = 0x00. Us ese valor como cdigo personalizado al verlo libre. void slcd_clear(void) { putc(0xFE); // Prefijo de comando putc(LCD_CLEAR); // Enviar instruccin Clear Display } El valor 0xFE lo tom arbitrariamente. Como consecuencia, se podrn visualizar en el LCD todos los caracteres de su tabla CGROM excepto el que tenga el cdigo 0xFE. Dado que ese carcter corresponde a la segunda mitad de la tabla, es un garabato que vara de un modelo de LCD a otro. Creo que se puede vivir sin l. Ahora pasemos a comentar el programa del segundo microcontrolador PIC. La mitad del cdigo de rs232lcd.c tiene funciones para menejar el LCD en bajo nivel. El resto de rs232lcd.c gestiona la recepcin de datos RS232. En este programa el uso de interrupciones y del buffer circular es vital e imprescindible. Este PIC debe recibir todos los datos posibles del otro PIC porque no tiene forma de decirle esprame, que estoy ocupado ejecutando la instruccin anterior. Por eso el tamano del buffer circular es 50: era lo que quedaba de RAM. El PIC16F84A no tiene USART y mucho menos interrupciones que anuncien la llegada de datos seriales. Pero sabiendo que los datos RS232 empiezan con un bit de Start (transicin de 1 a 0) podemos usar esa seal para empezar a leer el dato. Los pines de las interrupciones RB0/INT y de Cambio de PORTB ya estaban acaparados, as que tuve que ingenirmelas para trabajar con el Timer0. En este programa el Timer0 no temporiza, cuenta los flancos de bajada del pin RA4/T0CKI. Cuando el conteo pase de 255 a 0 (desbordamiento) se dispara su interrupcin. El conteo como tal no me interesa, solo quiero una interrupcin en el flanco de bajada. Por eso pongo el registro TMR0 a 255. En los programas las rutinas putc y getc estn calibradas para compilar con BoostC y para el PIC16F84A usando un XTAL de 4MHz. Pueden no funcionar correctamente si se cambian estos factores. En rs232lcd.c las funciones lcd_read y lcd_write son para PICs con XTAL < 10 MHz.

CURSO_MICROS

P g i n a | 231

El MSSP en modo I2C El bus I2C Protocolo del bus I2C Caractersticas del bus I2C El I2C (Inter Integrated Circuits) es un bus de comunicaciones serial sncrono de dos lneas que fue originalmente desarrolado por Philips Semiconductors (ahora nxp semiconductors) desde los inicios de los 80. Hoy es un estndar aceptado y respaldado por cientos de fabricantes de dispositivos semiconductores. El bus I2C permite la comunicacin entre mltiples dispositivos (en teora ms de 1000), todos conectados paralelamente a las dos lneas. Las transferencias de datos siempre se realizan entre dos dispositivos a la vez y en una relacin maestro esclavo. Los dispositivos maestros son normalmente los microcontroladores y los dispositivos esclavos pueden ser memorias, conversores DAC y ADC, controladores de LCD, sensores de todos los tipos, etc. Ahora bien, para que todos los dispositivos se puedan comunicar sin entorpecerse unos y otros, sin que haya prdidas o colisiones en las transferencias de datos, sin que los dispositivos rpidos se desentiendan de los dispositivos lentos, etc., se deben de seguir ciertas reglas estndar, cierto protocolo. Todas las especificaciones software y hardware del protocolo del bus I2C estn descritas en el I2C-bus specification and user manual. Es un documento PDF de 50 pginas (la versin que veo en este momento: rev. 03, de 2007). No obstante, si reducimos las funciones del bus I2C a redes donde solo haya un dispositivo maestro y uno o varios (hasta 112) dispositivos esclavos, si limitamos la velocidad de transferencia a un mximo de 1 Mbit/s (que no es poca cosa, verdad?), entonces el estndar I2C se simplifica ms o menos a las siguientes cinco pginas.

Topologa del bus I2C. Consideremos entonces las siguientes caractersticas:

CURSO_MICROS

P g i n a | 232

Las transferencias de datos se llevan a cabo mediante dos lneas: lnea serial de datos SDA y lnea serial de reloj SCL. Ambas son bidireccionales. SDA se encarga de conducir los datos entre el dispositivo maestro y los esclavos. SCL es la seal de reloj que sincroniza los datos que viajan por la lnea SDA. El dispositivo maestro (C) es quien siempre tiene la iniciativa de la comunicacin: el maestro genera la seal de reloj y controla cuando se transmiten o reciben los datos. Puede haber varios esclavos en la red I2C, pero el maestro solo se comunica con uno a la vez. Por eso cada dispositivo esclavo debe ser identificado por una direccin nica.

Hay muchos conceptos ms en el estndar. Algunos sern descritos en lo sucesivo y otros no nos conciernen directamente. Ante cualquier duda no resuelta aqu, puedes revisar el documento de la especificacin citado antes. Transferencias de datos Los datos que se transfieren por el bus I2C deben viajar para decirlo fcil en fo rma de paquetes, aqu llamados transferencias. Como se ve en la siguiente figura, una transferencia empieza con un START y termina con un STOP. Entre estas seales van los datos propiamente dichos. Cada dato debe ser de 8 bits (1 byte) y debe ir seguido de un noveno bit, llamado bit de reconocimiento (ACK o NACK).

Transferencias de datos sobre el bus I2C. La transferencia mostrada arriba tiene dos bytes pero puede varios ms (sin restriccin) o puede haber un solo byte por paquete. Los datos son transferidos por la lnea SDA y son acompaados y sincronizados por los pulsos de reloj de la lnea SCL. Para transmitir un bit primero hay que poner la lnea SDA a 1 0 segn sea el caso, y luego colocar un pulso en la lnea SCL. Los datos pueden viajar de ida y de vuelta por SDA sin colisionar porque es el maestro quien controla cundo se transmite o recibe un dato. De ese modo, el control de SDA

CURSO_MICROS

P g i n a | 233

puede ser asumido tanto por el maestro como por el esclavo y ambos dispositivos podrn intercambiar los roles de transmisor o receptor. Eso s, en cualquier caso, el control de la lnea SCL siempre (excepto en el Clock Stretching) es asumido por el maestro. Condicines de Start, Stop y Start Repetido Como dijimos, los paquetes de datos transferidos por el bus I2C deben ir enmarcados por un Start y un Stop. Ambas seales son generadas por el maestro. Una condicin de START es una transicin de Alto a Bajo en la lnea SDA cuando SCL est en Alto. Se le representa por la letra S. Despus de Start el bus se considera ocupado. Una condicin de STOP es una transicin de Bajo a Alto en la lnea SDA mientras SCL est en Alto. Est simbolizada por la letra P. Despus de Stop las dos lneas estn en Alto y el bus se considera libre. Se usa Stop para cerrar la transferencia de un paquete de datos o para abortar una transferencia previa que qued truncada.

Condiciones de START y STOP. La seal de una condicin de START repetido es exactamente igual a la de START. La diferencia es de tipo ocacional: aunque en principio cada transferencia debe ir enmarcada por un Start y un Stop, el estndar contempla la posibilidad de iniciar una nueva transferencia sobre una anterior que no ha sido cerrada con un Stop. El Start de la nueva transferencia se llama enconces Start Repetido y su smbolo es Rs. Este punto lo entenderemos mejor en las prcticas.

El bit de Reconocimiento (ACK o NACK) Volvindonos a referir a la figura de Transferencias de datos sobre el bus I2C., cada byte transferido debe ir seguido de un noveno bit, llamado acknowledge bit (bit de reconocimiento, en ingls). Este bit siempre debe ser devuelto por el dispositivo receptor (maestro o esclavo) tras cada byte recibido. Si el bit de reconocimiento es 0 significa que el dato fue reconocido y aceptado. Este bit se denomina ACK.

CURSO_MICROS

P g i n a | 234

Si el bit de reconocimiento es 1 significa que el dato recibido an no es aceptado. Se usa este mecanismo para indicar que el receptor est ocupado realizando alguna tarea interna. Este bit se denomina NACK.

El Byte de Control Como se sabe, las comunicaciones por el bus I2C se llevan a cabo siguiendo la relacin maestro esclavo. Eso significa que es el maestro (microcontrolador) quien ordena con cul esclavo se va a comunicar o si los siguientes datos se van a transmitir o recibir; el esclavo solo obedece. Pues bien, esa orden viaja en el primer byte de cada transferencia y es ms conocido como byte de control. Segn lo mostrado en la siguiente figura, 7 bits del byte de control contienen la direccin del esclavo con el cual se desea entablar la comunicacin y el bit R/W establece si los siguientes bytes sern de lectura o escritura. Como siempre, R/W = 0 indica una escritura y R/W = 1 indica una lectura.

Formato del byte de control (primer byte). Todos los esclavos deben recibir el byte de control, pero solo el que halle su direccin en l ser el que prosiga la comunicacin. Los dems esclavos se deben mantener al margen hasta un nuevo aviso (otra condicin de Start). Velocidad de transferencia de datos En este sentido el estndar del bus I2C soporta cuatro modos de operacin: Standard Mode, con una velocidad de hasta 100 kbit/s. Fast mode, con una velocidad de hasta 400 kbit/s. Fast mode plus, con una velocidad de hasta 1 Mbit/s. High-speed mode, con una velocidad de hasta 3.4 Mbit/s.

Los valores lmites implican que los dispositivos ms rpidos son compatibles con los dispositivos ms lentos; lo contrario, por supuesto, no es factible. As, entenderemos que todos ellos podran trabajar en una misma red si operan, por ejemplo, a 20 kHz, 50 kHz 100 kHz. Como cada bit de dato transferido sobre la lnea SDA debe ser validado por la seal de reloj SCL, podemos deducir que la velocidad de transferencia est determinada por la frecuencia de la seal de SCL. Por ejemplo, una velocidad de 100 kbits/s implica que cada bit se transmite en 1s/100k = 10s, lo que nos dice que cada semiperiodo de la seal de reloj vale en promedio 5 s. Estos datos se detallan en el Estndar I2C y tambin suelen ir indicados en los datasheets de los dispositivos I2C.

CURSO_MICROS

P g i n a | 235

El mdulo MSSP en modo I2C Lo que veremos es el mdulo de los PIC16 y PIC18 que realiza las funciones de interface I2C a nivel hardware. El MSSP o Master Synchronous Serial Port puede funcionar en modos I2C y SPI, los dos protocolos seriales ms conocidos despus del RS232. El modo I2C soporta a su vez los siguientes tres, de los cuales ahora estudiaremos el primero:

Modo I2C de maestro nico. El microntrolador trabaja como maestro, controlando uno o varios dispositivos esclavos como EEPROMs, termmetros, etc. Modo I2C de esclavo. El microcontrolador trabajar como esclavo frente a algn otro microcontrolador maestro. Modo I2C de maestros mltiples. Es una extensin del primer modo, solo que ahora el microcontrolador compartir la red I2C con otros microcontroladores maestros. Puede inclusive alternar su rol entre maestro y esclavo.

Registros del MSSP en I2C Master mode Los principales registros que conducen las operaciones del mdulo MSSP son: SSPBUF (Buffer de Recepcin y Transmisin). Es el registro donde se cargan los datos a transmitir y donde se depositan los datos que llegan. Como el bus I2C es half duplex, los datos pueden viajar en ambas direcciones pero no al mismo tiempo. El maestro debe controlar el trfico. SSPADD. Es el registro que establece la velocidad de transferencia de datos. Su nombre viene de Address Register, pero la direccin a que hace alusin es solo para el modo SPI. SSPCON (Registro de Control del SSP). En los PIC18 se llama SSPCON1. SSPCON2 (Registro de Control 2 del SSP). SSPSTAT (Registro de Estado del SSP).

En seguida presentamos los mapas de bits de los registros de control y estado a los que nos referiremos en adelante. Sus nombres no reflejan del todo sus funciones. Veremos luego que hay algunos bits de control en el registro de estado y viceversa. Los bits sombreados no tienen efecto en el modo I2C maestro. SSPCON WCOL SSPOV SSPEN CKP SSPM3 SSPCON2 GCEN ACKSTAT ACKDT ACKEN RCEN SSPSTAT SMP CKE D/A P S SSPM2 SSPM1 SSPM0 PEN RSEN SEN R/W UA BF

CURSO_MICROS

P g i n a | 236

Configuracin del I2C Master mode Antes de realizar cualquier operacin hay que habilitar el mdulo MSSP seteando el bit SSPEN (Synchronous Serial Port Enable) y seleccionar I2C Master mode configurando los bits SSPM3:SSPM0 a 0b1000. Con esto el MSSP tomar el control de los pines SCL y SDA, aunque habr que asegurarse de que estn configurados como entradas para permitir que acten como de drenador abierto. Velocidad de transferencias de datos Recordemos que cada bit de dato se valida con un pulso del reloj. Por tanto la velocidad de transferencia de datos ser igual a la frecuencia de la seal de SCL. Esta frecuencia depende del valor del registro SSPADD y por supuesto de Fosc (en nuestros PIC16F es el valor del XTAL). Se calcula con la siguiente frmula:

Se dice que la Interferencia Electromagntica (EMI) solo afecta las transmisiones cuando la velocidad del bus es de alrededor de 400 kbps (Fast mode). Para evitar esto, el mdulo MSSP tiene incorporado un filtro que adapta ligeramente las seales para que sean inmunes a dicha interferencia. Este filtro se llama Slew Rate Control y se habilita seteando el bit SMP. No tiene mucho sentido habilitar el filtro para velocidades lejanas a 400 kHz. //************************************************************************ // Configurar en mdulo MSSP en modo I2C Maestro y con // frecuencia de reloj de 100 kHz //************************************************************************ void i2c_init(void) { // Habilitar el mdulo MSSP y ponerlo en modo de Maestro I2C SSPCON = 0x28; // Poner Frecuencia de SCL a 100 kHz. // SSPADD = 4 MHz/(4*100kHz) 1 = 9 SSPADD = 9; // Una frecuencia de SCL de 100 kHz no requiere Slew Rate Control SSPSTAT.SMP = 0; } Condicin de Start Para iniciar una condicin de Start, se setea el bit SEN (Start Enable). Si el Start inicia normalmente, se activar el bit S y cuando termine satisfactoriamente el bit SEN se limpiar por hardware. Al trmino del Start tambin se activa el bit SSPIF (del registro PIR1) y se puede usar para generar una interrupcin si se programa.

CURSO_MICROS

P g i n a | 237

//************************************************************************ // Enva una Condicin de START. void i2c_start(void) { SSPCON2.SEN = 1; // Iniciar START while(SSPCON2.SEN) // Esperar a que termine de enviarse continue; } Condicin de Repeated Start Para iniciar una condicin de Start repetido, se setea el bit RSEN (Repeated Start Enable). Si el Start repetido inicia normalmente, se activar el bit S y luego de terminar satisfactoriamente el bit RSEN se limpiar por hardware. Al trmino del Start Repetido tambin se activa el flag SSPIF (del registro PIR1) y se puede usar para generar una interrupcin si se desea. //************************************************************************ // Enva una Condicin de Repeated START. //************************************************************************ void i2c_restart(void) { SSPCON2.RSEN = 1; // Iniciar Repeated START while(SSPCON2.RSEN) // Esperar a que termine de enviarse continue; } Condicin de Stop Una condicin de Stop se enva seteando el bit PEN (stoP Enable). Una vez detectado el Stop en el bus, se activar el bit P y cuando termine, el bit PEN se limpiar automticamente. Al finalizar el Stop tambin se setear el flag SSPIF (del registro PIR1) para dar paso a una interrupcin si as se desea. //************************************************************************ // Enva una Condicin de STOP. //************************************************************************ void i2c_stop(void) { SSPCON2.PEN = 1; // Iniciar STOP while(SSPCON2.PEN) // Esperar a que termine de enviarse continue; } Transmitir Dato y Recibir bit ACK/NACK El byte (de dato o de comando) a transmitir se debe cargar en el registro SSPBUF. Esa accin setear el bit BF (Buffer Full) indicando el dicho registro est lleno. En seguida el dato es movido al registro oculto SSPSR y de all saldr afuera. Despus de transmitirse todo el dato, SSPBUF queda libre otra vez y el bit BF se limpiar automticamente.

CURSO_MICROS

P g i n a | 238

Si el esclavo recibe el dato correctamente y lo admite, entonces devolver una seal de reconocimiento enviando un bit 0 llamado ACK (Acknowledge). De lo contrario, responder con un bit 1 llamado NACK (Not Acknowledge). En cualquiera de los casos el maestro recibir el bit ACK/NACK y lo reflejar en el bit ACKSTAT. Al final de esta secuencia se vuelve a activar por hardware el bit SSPIF (del registro PIR1) y podr disparar una interrupcin si fue habilitada. //************************************************************************ // Enva el byte data y devuelve el valor del bit ACK/NACK recibido. // ACK = 0 y NACK = 1. //************************************************************************ char i2c_write(char data) { SSPBUF = data; // Colocar dato a enviar PIR1.SSPIF = 0; // Limpiar SSPIF // while(sspstat.BF == 1) // Esperar a que se termine // continue; // de envar el dato while(PIR1.SSPIF == 0) // Esperar a que llegue el bit ACK/NACK continue; // if(SSPCON2.ACKSTAT) // Leer bit ACK/NACK recibido return 1; // Esto es un NACK else return 0; } // Esto es un ACK

Recibir Dato y Transmitir bit ACK/NACK Un esclavo no puede enviar un dato cuando quiera. Es el maestro quien le ordena que lo haga. Para esto debemos setear el bit RCEN (Receive Enable). Cuando el dato termine de llegar se depositar en el registro SSPBUF y luego se activar por hardware el bit BF (Buffer Full) y se limpiar el bit RCEN. El bit BF es automticamente limpiado tras leer SSPBUF. Claro, el bit SSPIF (del registro PIR1) tambin se setear. SSPIF se limpia por software. Ahora nos toca a nosotros responder con el bit ACK/NACK. Para ello primero debemos escribir el 0 (Ack) 1 (Nack) en el bit ACKDT (Acknowledge Data). Luego podemos iniciar su envo seteando el bit ACKEN (Ack Enable). ACKEN se limpiar automticamente despus de que nuestra respuesta se haya enviado satisfactoriamente. //************************************************************************ // Lee un byte de dato y enva el bit ACK/NACK. // ack = 0 es ACK y ack = 1 es NACK. //************************************************************************ char i2c_read(char ack) { char data;

CURSO_MICROS

P g i n a | 239

SSPCON2.RCEN = 1;

// Habilitar recepcin de dato

while(SSPCON2.RCEN) // Esperar a que termine de continue; // llegar el dato data = SSPBUF; // Leer dato llegado

if(ack == 1) // Preparar bit ACK/NACK a enviar SSPCON2.ACKDT = 1; // Esto es NACK else // SSPCON2.ACKDT = 0; // Esto es ACK SSPCON2.ACKEN = 1; // Iniciar envo de bit ACK/NACK while(SSPCON2.ACKEN) continue; // return data; } Los registros SSPSTAT, SSPCON y SSPCON2 ste es el registro SSPSTAT: Los bits sombreados no cuentan en modo I2C Maestro, los bits P, S y R/W son indicadores secundarios que raramente se usan. Los bits BF (de solo lectura) y SMP (de lectura y escritura) si son preponderantes. SMP CKE Bit 7 SMP: Slew Rate Control bit En modo Maestro o Esclavo: 1 = Inhabilitar Slew rate control, para frecuencias de 100 kHz y 1 MHz 0 = Habilitar Slew rate control, para frecuencia de 400 kHz SMBus Select bit En modo Maestro o Esclavo: 1 = Habilitar entradas especficas de SMBus 0 = Inhabilitar entradas especficas de SMBus Data/Address bit Vlido solo en modo Esclavo: 1 = Indica que el ltimo byte transmitido o recibido fue un dado 0 = Indica que el ltimo byte transmitido o recibido fue una direccin Stop bit 1 = Indica que se acaba de detectar un bit Stop D/A P S R/W UA BF Bit 0 // Esperar envo completado

CKE:

D/A:

P:

CURSO_MICROS

P g i n a | 240

SMP CKE D/A P 0 = No se detect un Stop

R/W

UA

BF

S:

Nota: este bit se limpia tras el Reset y cuando se limpia SSPEN Start bit 1 = Indica que se acaba de detectar un bit Start 0 = No se detect un Start Nota: este bit se limpia tras el Reset y cuando se limpia SSPEN Read/Write bit information (I2C mode only) En modo Esclavo: 1 = Lectura 0 = Escritura Nota: este bit contiene el valor del bit R/W del Byte de Control recibido. En modo Maestro: 1 = Hay una transmisin en progreso 0 = No hay transmisiones en progreso Nota: Haciendo OR entre este bit y los bits SEN, RSEN, PEN, RCEN o ACKEN indicar si el MSSP est disponible Update Address (10-bit Slave mode only) 1 = Indica que el usuario necesita actualizar la direccin del registro SSPADD 0 = La direccin no necesita actualizarse Buffer Full Status bit En modo de Transmisin: 1 = Recepcin completada, SSPBUF est lleno 0 = Recepcin incompleta, SSPBUF est vacio En modo de Recepcin: 1 = La transmisin de dato est en progreso, SSPBUF est lleno 0 = La transmisin de dato est completa, SSPBUF est vaco

R/W:

UA:

BF:

ste es el registro SSPCON: El bit CKP no vale en modo I2C Maestro. Todos los bits son de lectura.. En los PIC18 se llama SSPCON1. WCOLSSPOV SSPEN CKP SSPM3 SSPM2 SSPM1 SSPM0

CURSO_MICROS

P g i n a | 241

WCOLSSPOV Bit 7 WCOL:

SSPEN

CKP

SSPM3

SSPM2

SSPM1

SSPM0 Bit 0

Write Collision Detect bit En modo Maestro Transmisor: 1 = Colisin por intentar una escritura en SSPBUF estando el bus indispuesto. Se limpia por software 0 = No hay colisin En modo Esclavo Transmisor: 1 = Colisin por escribir en SSPBUF mientras se transmita el dato previo. Se limpia por software 0 = No hay colisin En modo Recepcin (de Maestro o Esclavo):

No importa SSPOV: Receive Overflow Indicator bit Vlido solo en modo de Recepcin: 1 = Desbordamiento por recibir un byte estando an SSPBUF ocupado por un dato previo. Se limpia por software 0 = No hay desbordamiento Synchronous Serial Port Enable bit 1 = Habilita el puerto serial y configura pines SDA y SCL como pines seriales 0 = Inhabilita el puerto serial y configura los pines SDA y SCL para pines I/O Nota: los pines SDA y SCL se deben configurar como entrada o salida SCK Release Control bit Vlido solo en modo Esclavo: 1 = Liberar lnea SCL 0 = Mantener lnea SCL abajo (Clock stretch). Para asegurar data setup time

SSPEN:

CKP:

SSPM3: Synchronous Serial Port Mode Select bits SSPM0: 1111 = I2C Slave mode, 10-bit address with Start and Stop bit interrupts enabled

CURSO_MICROS

P g i n a | 242

WCOLSSPOV SSPEN CKP SSPM3 SSPM2 SSPM1 SSPM0 1110 = I2C Slave mode, 7-bit address with Start and Stop bit interrupts enabled 1011 = I2C Firmware Controlled Master mode (Slave idle) 1000 = I2C Master mode, clock = Fosc/(4*(SSPADD+1)) 0111 = I2C Slave mode, 10-bit address 0110 = I2C Slave mode, 7-bit address Nota: las combinaciones de bits no especificadas aqu estn reservadas o implementadas solo en modo SPI.

ste es el registro SSPCON2. GCENASKSTAT Bit 7 GCEN: General Call Enable bit (Slave mode only) 1 = Habilitar interrupcin cuando se reciba un General Call Address 0 = Inhabilitar el General Call Address ACKSTAT: Acknowledge Status bit (Master Transmit mode only) 1 = Se recibi un NACK del esclavo 0 = Se recibi un ACK del esclavo Acknowledge Data bit (Master Receive mode only) 1 = Not acknowledge, NACK 0 = Acknowledge, ACK Nota: es el valor que se transmitir cuando se inicie la secuencia Acknowledge al final de una recepcin. Acknowledge Sequence Enable bit (Master Receive mode only) 1 = Iniciar secuencia Acknowledge. Se enva el valor del bit ACKDT. Se limpia automticamente por hardware 0 = Secuencia Acknowledge en suspenso Receive Enable bit (Master mode only) 1 = Habilitar modo de recepcin de dato para I2C 0 = Recepcin en suspenso Stop Condition Enable bit (Master mode only) 1 = Iniciar condicin de Stop. Se limpia automticamente por hardware ACKDT ACKEN RCEN PEN RSEN SEN Bit 0

ACKDT:

ACKEN:

RCEN:

PEN:

CURSO_MICROS

P g i n a | 243

GCENASKSTAT ACKDT ACKEN RCEN PEN RSEN 0 = Condicin de Stop en suspenso RSEN: Repeated Start Condition Enable bit (Master mode only)

SEN

1 = Iniciar condicin de Start repetido. Se limpia automticamente por hardware 0 = Condicin de Start repetido en suspenso Start Condition Enabled/Stretch Enabled bit En modo Maestro: 1 = Inicia condicin de Start. Se limpia automticamente por hardware 0 = Condicin de Start en suspenso En modo Esclavo: 1 = Habilitar Clock Stretching para esclavo transmisor y esclado receptor 0 = Habilitar Clock Stretching solo para esclavo transmisor Las Interrupciones del mdulo I2C En las comunicaciones RS-232 los datos suelen viajar tan lento que el microcontrolador puede aprovechar los tiempos que duran las transferencias para ejecutar otras funciones. En las comunicaciones I2C el uso de las interrupciones para detectar el inicio o final de los datos solo es beneficioso para el microcontrolador cuando opera en modo de Esclavo. La interrupcin del mdulo MSSP puede ser disparada por cualquiera de los eventos que activa el flag SSPIF. El bit SSPIF se limpia por software. La interrupcin del mdulo MSSP se habilita seteando los bits SSPIE (Synchronous Serial Port Interrupt Enable), PEIE y GIE. INTCON GIE PEIE TMR0IE PIE1 PSPIE ADIE RCIE PIR1 PSPIF ADIF RCIF INTE TXIE TXIF RBIE TMR0IF SSPIE CCP1IE SSPIF CCP1IF INTF RBIF TMR2IE TMR1IE TMR2IF TMR1IF

SEN:

Memorias EEPROM 24xx128 Las EEPROM I2C se estudian con mayor cobertura en la seccin control de dispositivos I2C de la web. Aqu nos limitaremos al modelo 24128 resaltando los procedimientos de accesos aleatorio y secuencial de datos. Esos mecanismos se repiten en los otros dispositivos I2C. Los modelos disponibles de Microchip son 24AA128, 24LC128 y 24FC128. Su principal diferencia es que trabajan a diferente velocidad de transferencia. Fuera de eso comparten las siguientes caracterscticas:

CURSO_MICROS

P g i n a | 244

Capacidad de 128 kbits = 16 kbytes. Tiempo de escritura mximo de 5 ms. Modo de escritura por pgina de 64 bytes. Conexin en cascada hasta de 8 dispositivos. Frecuencia mxima de reloj de 400 kHz (Full Speed mode). Retencin de datos > 200 aos. 1,000,000 de ciclos de lectura escritura.

Descripcin de pines

Diagrama de pines de la EEPROM 24xx128. A0, A1 y A2. Pines que establecen parte de la direccin de esclavo de este dispositivo. (Leer siguiente seccin.) Vss y Vcc. Tierra y alimentacin del dispositivo. SDA y SCL. Lnea serial de datos y lnea serial de reloj. WP (Write Protection). Conectado a tierra desactiva la proteccin de escritura, es decir, el contenido de la memoria podr ser ledo pero no escrito. Si WP se conecta a VCC la memoria se podr leer y escribir.

Direccin del dispositivo Recordemos que cada dispositivo esclavo conectado al bus I2C debe estar identificado por una direccin de 7 bits. En las EEPROM de la familia 24xxx la direccin de esclavo, en binario, es 1010xxx, siendo xxx la parte reconfigurable por los pines A2, A1 y A0 del dispositivo. As se podrn formar 8 direcciones diferentes para conectar hasta 8 EEPROMs de este tipo. Como sabrs, la direccin de esclavo Slave Address debe ir en El Byte de Control y est acompaada del bit R/W, que indica si los siguientes bytes sern de lectura (R/W = 1 = Read) o de escritura (R/W = 0 = Write).

CURSO_MICROS

P g i n a | 245

El byte de control (Direccin de esclavo + bit R/W). Acceso a la EEPROM 24xx128 Lectura y escritura aleatorias de bytes Hay dos formas de realizar operaciones de lectura y escritura de datos en las EEPROM 24xx128: una individualmente (acceso a un byte por transferencia) y la otra es en bloques (varios bytes por transferencia). Cada transferencia empieza con un START y termina con un STOP. En este apartado nos enfocamos en la primera forma, conocida como acceso aleatorio porque se debe especificar la direccin de cada dato accedido. La locacin de memoria a acceder depende de un registro interno llamado Puntero de memoria, el cual puede llegar a ser de 16 bits (2 bytes).

Secuencia de escritura de un byte en la EEPROM 24xx128. La figura de arriba indica que para escribir un Data Byte en la direccin Address de la memoria se debe seguir la siguiente secuencia: Enviar una Condicin de START (iniciar transferencia). Enviar el byte de control para escritura (Slave address + Write). Enviar el byte alto de Address. Enviar el byte bajo de Address. Enviar el Data Byte. Enviar una Condicin de STOP (cerrar transferencia).

CURSO_MICROS

P g i n a | 246

Tras la Condicin de STOP empieza el ciclo de escritura interno del dato enviado. Este ciclo dura a lo mucho 5 ms. Hay que poner un delay.

Ahora pasemos al proceso de lectura de una posicin aleatoria de la EEPROM 24128. De nuevo, solo seguimos los pasos que nos indica el datasheet, graficados en el siguiente esquema:

Secuencia de lectura de un byte de la EEPROM 24xx128. El esquema indica que para leer un Data Byte de la direccin Address de la memoria se deben seguir los siguientes pasos: Enviar una condicin de START. Enviar el byte de control para escritura (Slave address + Write). Enviar el byte bajo de Address. Enviar el byte alto de Address. Enviar una condicin de START (llamada START repetido aunque sea lo mismo). Enviar el byte de control para lectura (Slave address + Read). Leer el byte de dato y devolver un NACK. Enviar una condicin de STOP.

Nota que, aunque vayamos a leer un dato de la EEPROM, primero debemos especificar de qu direccin, es decir, debemos escribir su direccin (en el Puntero de memoria). Por eso el primer byte de control es para escritura. Luego se vuelve a enviar el Byte de Control, esta vez para la lectura del dato en s.

CURSO_MICROS

P g i n a | 247

Programacion de la EEPROM 24xx128 Prctica 1 Acceso aleatorio a la eeprom 24xx128 Lo que hace el programa es grabar cada uno de los caracteres de una cadena de texto en las primeras posiciones de la EEPROM serial y a continuacin los lee todos de all y los visualiza en la consola terminal. El circuito La conexin de los pines A2, A1 y A0 establecen la direccin de esclavo a 0b1010000. Segn el datasheet, el valor recomendable de las resistencias de pull up para velocidades de bus menores o iguales a 100 kHz es de unos 10 k. No es fcil dar un valor exacto porque depender tanto de la velocidad del bus como de su capacitancia. Este ltimo parmetro a su vez depende en gran medida del circuito (ni siquiera es lo mismo un circuito de placa impresa que uno montado en un breadboard).

Nota: para la simulacin en Proteus VSM las resistencias deben ser digitales. Se pueden escoger las partes PULL UP o cambiar una resistencia analgica a digital editando su ventana de propiedades. El cdigo fuente

//////////////////////////////////////////////////////////////////////////////// // File Name: 24128rand.c // Processor: PIC16F87xA // Compiler: BoostC/C++

CURSO_MICROS

P g i n a | 248

// Author: Shawn Johnson // Purpose: Acceso aleatorio a la EEPROM serial 24xx128 //////////////////////////////////////////////////////////////////////////////// #include <system.h> #include <string.h> #include usart.h #include i2c.h

// Contiene funcin strlen()

#pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 void write_24128(unsigned int address, char data); char read_24128(unsigned int address); void main(void) { char c; unsigned int a; char txt[] = www.cursomicros.com ; unsigned char lon = strlen(txt); // Obtener longitud de txt i2c_init(); usart_init(); // 91 kHz // 9600 - 8N1

puts(\n\r Acceso aleatorio a la EEPROM 24xx128 \n\r); // Escribir todos los caracteres de txt en las primeras posiciones de // la 24128 for(a=0; a<lon; a++) { c = txt[a]; // Obtener elemento a de txt write_24128(a, c); // Escribir c en direccin a } // Leer las lon primeras posiciones de la 24128 y mostrarlas en la // consola for(a=0; a<lon; a++) { c = read_24128(a); // Leer direccin a putc; // Enviar a terminal } sleep(); } // Entrar en modo Standby

//**************************************************************************** // Escribe el dato data en la direccin address de la EEPROM serial. //**************************************************************************** void write_24128(unsigned int address, char data) { i2c_start(); // START i2c_write(0xA0); // Slave address + Write i2c_write(address>>8); // Address high byte i2c_write(address); // Address low byte i2c_write(data); // Data to EEPROM i2c_stop(); // STOP delay_ms(5); // Tiempo de ciclo de escritura interno

CURSO_MICROS

P g i n a | 249

} //**************************************************************************** // Lee un byte de dato de la direccin address de la EEPROM serial. //**************************************************************************** char read_24128(unsigned int address) { char data; i2c_start(); // START i2c_write(0xA0); // Slave address + Write i2c_write(address>>8); // Address high byte i2c_write(address); // Address low byte i2c_restart(); // Repeated START i2c_write(0xA0|0x01); // Slave address + Read data = i2c_read(1); // Read data & send NACK i2c_stop(); // STOP return data; } Libreria para bus I2C ////////////////////////////////////////////////////////////////////////////// // File Name: i2c.h // Processor: PICmicros con MSSP // Compiler: BoostC // Author: Shawn Johnson // Purpose: Librera de funciones para el mdulo MSSP en modo I2C Master ////////////////////////////////////////////////////////////////////////////// #include <system.h> //**************************************************************************** // Configuracin de la frecuencia del bus I2C //**************************************************************************** #define XTALfreq 4000000 // Frecuencia XTAL = 4 MHz #define I2Cfreq 90000 // Frecuencia bus I2C = 90 kHz //**************************************************************************** // Prototipos de funciones //**************************************************************************** void i2c_init(void); // Inicializa el bus I2C void i2c_start(void); // Enva un START void i2c_restart(void); // Enva un START Repetido void i2c_stop(void); // Enva un STOP char i2c_write(char data); // Enva un byte y recibe el bit ACK/NACK char i2c_read(char ack); // Recibe un byte y enva el bit ACK/NACK ////////////////////////////////////////////////////////////////////////////// // File Name: i2c.c // Processor: PICmicros con MSSP // Compiler: BoostC // Author: Shawn Johnson // Purpose: Librera de funciones para el mdulo MSSP en modo I2C Master //////////////////////////////////////////////////////////////////////////////

CURSO_MICROS

P g i n a | 250

#include i2c.h //**************************************************************************** // Inicializa el mdulo MSSP para operar en modo I2C maestro. // El Slew rate control se debe habilitar para frecuencias cercanas a 400 kHz. //**************************************************************************** void i2c_init(void) { #ifdef _PIC16 sspcon = 0x28; // Habilitar MSSP y en modo I2C master #elif _PIC18 sspcon1 = 0x28; // Habilitar MSSP y en modo I2C master #endif sspadd = (XTALfreq/(4*I2Cfreq))-1; // SSPADD = Fosc/(4*clock) - 1; sspstat.SMP = 1; // Inhabilitar Slew rate control } //**************************************************************************** // Enva una Condicin de START. //**************************************************************************** void i2c_start(void) { sspcon2.SEN = 1; // Iniciar START while(sspcon2.SEN) // Esperar a que termine de enviarse continue; } //**************************************************************************** // Enva una Condicin de Repeated START. //**************************************************************************** void i2c_restart(void) { sspcon2.RSEN = 1; // Iniciar Repeated START while(sspcon2.RSEN) // Esperar a que termine de enviarse continue; } //**************************************************************************** // Enva una Condicin de STOP. //**************************************************************************** void i2c_stop(void) { sspcon2.PEN = 1; // Iniciar STOP while(sspcon2.PEN) // Esperar a que termine de enviarse continue; } //**************************************************************************** // Enva el byte data y devuelve el valor del bit ACK/NACK recibido. // ACK = 0 y NACK = 1. //**************************************************************************** char i2c_write(char data) { sspbuf = data; // Colocar dato a enviar

CURSO_MICROS

P g i n a | 251

pir1.SSPIF = 0;

// Limpiar SSPIF

while(pir1.SSPIF == 0) // Esperar a que llegue el bit ACK/NACK continue; // if(sspcon2.ACKSTAT) // Leer bit ACK/NACK recibido return 1; // Esto es un NACK else return 0; // Esto es un ACK } //**************************************************************************** // Lee un byte de dato y enva el bit ACK/NACK. // ack = 0 es ACK y ack = 1 es NACK. //**************************************************************************** char i2c_read(char ack) { sspcon2.RCEN = 1; // Habilitar recepcin de dato if(ack == 1) // Preparar bit ACK/NACK a enviar sspcon2.ACKDT = 1; // Esto es NACK else // sspcon2.ACKDT = 0; // Esto es ACK while(sspcon2.RCEN) // Esperar a que termine de llegar el dato continue; // sspcon2.ACKEN = 1; while(sspcon2.ACKEN) continue; // return sspbuf; } Descripcin de los cdigos Respecto a write_24128: despues de cada byte enviado se recibe el correspondiente bit acknowledge, aunque no se tomen en cuenta porque se da por hecho que se tratan de ACKs (que el esclavo recoce todos los bytes). En el procedimiento de la funcin read_24128 el i2c_restart parece romper el paradigma segn el cual los datos deben viajar en paquetes (entre un START y un STOP). En realidad, no. Sucede que al enviar un START estando el bus ocupado, su seal ser similar a un STOP seguido de un START. Segn eso podemos ver el procedimiento como la transferencia de dos paquetes. En el primero se enva la direccin de memoria y en el segundo se recibe el dato ledo. La direccin de esclavo Slave address est contenida en 0xA0 = 10100000. Los bits 3, 2 y 1 valen 0 porque en el circuito los pines A2, A1 y A0 estn conectados a GND. El bit 0 vale inicialmente 0 para indicar una escritura y se pone a uno con 0xA0 |0x01 para cuando se desee una lectura. El cdigo de las funciones I2C ha sido ligeramente modificado respecto de lo descrito en la teora con fines prcticos o de eficiencia. // Iniciar envo de bit ACK/NACK // Esperar envo completado

CURSO_MICROS

P g i n a | 252

Acceso secuencial a la EEPROM 24XX128 Lectura Secuencial y Escritura por Pginas Por lo visto previamente, para mover un byte de dato a/desde la 24xx128 la transferencia (datos entre el START y STOP) deba incluir algunos bytes extras, como el byte de control (dos veces en las lecturas) y de direccin de memoria (hasta dos bytes). Este proceso puede ser pesado para algunas aplicaciones, donde se transfieran grandes cantidades de datos. Afortunadamente, tambin es posible mover varios bytes de datos por transferencia. A eso se le llama lectura secuencial y escritura por pginas. En general, bastar con especificar la direccin de la primera locacin a acceder. Despus de cada lectura o escritura, el puntero de memoria se incrementar automticamente para acceder a la siguiente locacin. Como lo evidencian los diagramas de tiempos, los procedimientos software seguidos en ambos casos es muy similar a como se haca con un nico byte: empieza igual y termina igual, solo vara la cantidad de data bytes transferidos y un detallito que a continuacin se describe. Por ejemplo, en la lectura la diferencia es que cada byte ledo debe ser respondido con un ACK, salvo el ltimo, el cual debe ser respondido con un NACK. Esto es compatible con la lectura de un solo byte ya que ah el nico byte es a la vez el ltimo.

Esquema de una lectura secuencial de N+1 datos. Por otro lado, para escribir un bloque de bytes se sigue el mismo procedimiento que para escribir un solo byte. El obstculo ahora es que el nmero de bytes enviados por transferencia es limitado y en rangos restringidos, segn el espacio de las pginas. Por eso se llama escritura por pginas. Ahora, qu son las pginas?

CURSO_MICROS

P g i n a | 253

Esquema de una escritura por pginas. Las EEPROM I2C poseen buffers internos donde reciben temporalmente los datos. En la 24xx128 este buffer es de 64 bytes, lo que le permite recibir hasta 64 bytes seguidos. Al cerrar la transferencia (con el STOP) el buffer entero ser volcado a las celdas de la memoria y empezar el ciclo de escritura interno. Lo bueno es que este ciclo durar lo mismo que cuando se escribe un solo byte, o sea, 5 ms a lo sumo. Segn el tamao del buffer interno, el cuerpo de la EEPROM se puede dividir en bloques o pginas. Para la 24xx128 estamos hablando de 16KB/64 = 512 pginas; cada pgina empieza en una direccin mltiplo de 64 y termina en una mltiplo de 64 menos 1. El punto es que una vez establecido el puntero de memoria, su valor se incrementar tras cada byte enviado, hasta llegar al lmite de la pgina actual, luego de lo cual volver a apuntar al inicio de la pgina. Como consecuencia, los siguientes bytes enviados sobreescribiran los datos all presentes. No olvides esa precaucin. Prctica 2 Acceso secuencial a la 24xx128 Repetiremos la tarea del anterior programa solo que ahora el texto se graba en una nica transferencia o paquete de datos. En la prctica anterior se grababa una cadena de texto de ms de 20 datos en la EEPROM serial. Los ms de 20 caracteres tomaban ms de 90 ms, cerca de 5 ms por cada uno, sin contar los bytes de control, bytes de direccin, etc. Con la presente prctica veremos cmo se acelera la grabacin de un bloque de datos.

CURSO_MICROS

P g i n a | 254

El circuito Es el mismo circuito de la prctica anterior.

El cdigo fuente

//////////////////////////////////////////////////////////////////////////////// // File Name: 24128seq.c // Processor: PIC16F87xA // Compiler: BoostC/C++ // Purpose: Acceso secuencial a la EEPROM serial 24xx128 //////////////////////////////////////////////////////////////////////////////// #include <system.h> #include <string.h> #include usart.h #include i2c.h

// Contiene funcin strlen()

#pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 void WritePage_24128(unsigned int address, char * p, unsigned char size); void ReadSeq_24128(unsigned int address, char * p, unsigned char size); void main(void) { char txt[] = www.cursomicros.com ; char buf[50]; unsigned char lon = strlen(txt); // Obtener longitud de txt i2c_init(); usart_init(); // 91 kHz // 9600 - 8N1

CURSO_MICROS

P g i n a | 255

puts(\n\r Acceso secuencial a la EEPROM 24xx128 \n\r); // Escribir todos los caracteres de txt en las primeras posiciones de // la 24128 WritePage_24128(0, txt, lon); // Leer en buf las lon primeras posiciones de la 24128 y mostrarlas en // la consola ReadSeq_24128(0, buf, lon); puts(buf); sleep(); // Entrar en modo Standby } //**************************************************************************** // Escribe los size primeros bytes del array p en la EEPROM 24128 // a partir de la direccion address. // Nota: los size bytes no deben rebasar la pgina actual. //**************************************************************************** void WritePage_24128(unsigned int address, char * p, unsigned char size) { unsigned char i; i2c_start(); // START i2c_write(0xA0); // Slave address + Write i2c_write(address>>8); // Address high byte i2c_write(address); // Address low byte for(i=0; i<size; i++) // i2c_write(p[i]); // Data to EEPROM i2c_stop(); // STOP delay_ms(5); // Tiempo de ciclo de escritura interno } //**************************************************************************** // Lee del array p los size bytes consecutivos de la EEPROM 24128 // a partir de la direccin address. // Tras cada byte ledo se enva un ACK, salvo en el ltimo, donde se enva // un NACK. //**************************************************************************** void ReadSeq_24128(unsigned int address, char * p, unsigned char size) { unsigned char i; i2c_start(); // START i2c_write(0xA0); // Slave address + Write i2c_write(address>>8); // Address high byte i2c_write(address); // Address low byte i2c_restart(); // Repeated START i2c_write(0xA0|0x01); // Slave address + Read for(i=0; i<size-1; i++) // p[i] = i2c_read(0); // Leer dato y enviar ACK p[i] = i2c_read(1); // Leer dato y enviar NACK i2c_stop(); // STOP }

CURSO_MICROS

P g i n a | 256

El conversor Analogico Digital Introduccin Por ms que actualmente nuestro alrededor est repleto por la tecnologa digital, sabemos que el mundo no naci as y que hay cosas que tampoco van a cambiar. No podemos cambiar la naturaleza analgica de los fenmenos naturales como la presin, la temperatura, la luminosidad, la electricidad, el magnetismo, etc. El transductor elemental que se utiliza para digitalizar las seales de estos fenmenos es el Conversor Analgico a Digital, ADC, que convierte una tensin elctrica en un valor numrico. De algn modo, cualquier otra seal puede llegar a manifestarse elctricamente, de all a tensin elctrica, y la tenemos. As es como funcionan por ejemplo los sensores de luz, de temperatura (de calor), etc. Conceptos bsicos Antes de entrar de lleno en la programacin del conversor ADC del PIC16F87xA vamos a conocer algunos conceptos que nos ayudarn para no perdernos en la teora. Resolucin y tensiones de referencia del ADC Un conversor ADC es un circuito que toma valores analgicos de tensin y los convierte en cdigos binarios. Los valores que definen los lmites de las tensiones a medir se denominan voltajes de referencia y se representan por Vref- (el mnimo) y Vref+ (el mximo). La resolucin del conversor queda determinada por la cantidad de bits que representan el resultado de la conversin. As, se pueden encontrar conversores de 8 bits, de 12 bits, etc. Un ADC de n bits puede representar hasta 2n valores digitales, de modo que a la entrada analgica igual a Vref- le asignar el 0 digital y la entrada igual a Vref+ le asignar el 2n-1 digital. A los otros valores analgicos se les asignar los otros 2n-2 valores digitales distribuidos equidistantemente. Entre Vref- y Vref+ se pueden concebir infinitos valores analgicos, pero con n bits solo se pueden formar 2n valores discretos diferentes. Por lo tanto habr valores analgicos que no podrn ser representados con exactitud. La diferencia entre dos valores analgicos correspondientes a dos valores digitales consecutivos se define como resolucin de voltaje de ADC.

Por ejemplo, en un ADC 10 bits con Vref- = 0 V y Vref+ = 5V, la resolucin alcanzada ser de (5-0)/1023 = 4.88 mV. Significa que el mximo error posible ser de 4.88/2 = 2.44 mV. Es poco usual encontrar aplicaciones donde Vref- sea diferente de GND = 0V y donde Vref+ sea diferente de VCC = 5V. En estas condiciones se puede aplicar una regla de

CURSO_MICROS

P g i n a | 257

tres para deducir que una entrada analgica Vin cualquiera (entre 0 y Vref+) ser convertida en un valor numrico que se puede calcular con la siguiente frmula:

El ADC de aproximaciones sucesivas Casi todos los mdulos ADC de los microcontroladores son de aproximaciones sucesivas. Funcionan con cuatro elementos bsicos: un comparador analgico, una lgica de control, un conversor digital analgico DAC y el reloj que gua los pasos de la conversin. Los DAC son mucho ms simples que los ADC y entregan resultados casi de inmediato. Cada nmero binario generado va siendo convertido en una tensin analgica Vout que luego se compara con la tensin de entrada que queremos medir Vin. Si son iguales (o los ms cercanos posible), eureka! Es el nmero binario que corresponde a Vin. As de simple.

Diagrama de bsico de un conversor ADC de aproximaciones sucesivas Ahora bien, siendo el conversor mostrado de 10 bits y pudindose generar hasta 1024 nmeros binarios distintos, se tendrn que hacer 1024 comparaciones? No, solo 10. El algoritmo a seguir es similar al que responde a la clsica pregunta capciosa: Si entre 100 bolitas hay solo una que pesa un poco ms que las otras, cuntas veces habr que llevarlas a una balanza para encontrarla? (rpta: 6) Para entender mejor cmo funciona este ADC vamos a imitar su operacin. Supongamos que el ADC trabaja con tensiones de referencia de 0 V y 5 V y que queremos medir una seal Vin de 4.00000 Volts. Como el ADC es de 10 bits, dar los 10 pasos mostrados en la siguiente tabla: Paso # 1 2 3 4 5 6 Binario Generado Vout (DAC) Vin (ADC) Vout < Vin ? D9 ... D0 (Voltios) (Voltios) 10 0000 0000 2.50244 4.00000 S, queda D9 11 0000 0000 3.75366 4.00000 S, queda D8 11 1000 0000 4.37927 4.00000 No, limpiar D7 11 0100 0000 4.06647 4.00000 No, limpiar D6 11 0010 0000 3.91000 4.00000 S, queda D5 11 0011 0000 3.98826 4.00000 S, queda D4

CURSO_MICROS

P g i n a | 258

Binario Generado Vout (DAC) Vin (ADC) Vout < Vin ? D9 ... D0 (Voltios) (Voltios) 7 11 0011 1000 4.02737 4.00000 No, limpiar D3 8 11 0011 0100 4.00782 4.00000 No, limpiar D2 9 11 0011 0010 3.99804 4.00000 S, queda D1 10 11 0011 0011 4.00293 4.00000 No, limpiar D0 Valor final 11 0011 0010 3.99804 El primer nmero binario generado tiene el bit D9 seteado. ste nmero se convierte en el valor analgico Vout, que despus se compara con Vin. Como la comparacin (Vout < Vin) es positiva nos quedamos con este bit. Despus se prueba seteando el siguiente bit, D8, y como la evaluacin (Vout < Vin) sigue siendo afirmativa tambin nos quedamos con este bit. En seguida se setea el bit D7; ahora la evaluacin (Vout < Vin) es negativa y debemos limpiar D7. Y se sigue con el resto de manera similar hasta alcanzar el bit D0. Paso # Como vemos, al final nos quedamos con el valor 11 0011 0010, que significa una tensin de 3.99804 V y que comparado con nuestros 4.00000 V nos da un error de 0.00196 V = 1.96 mV de 0.049%. Nada mal. Es fcil ver que a mayor resolucin en bits habr ms aproximacin.

El ADC de los PIC El mdulo ADC de los PIC16F87xA Los PIC16F877A y PIC16F874A tienen un ADC de aproximaciones sucesivas de 10 bits. Es uno solo pero est multiplexado para dar cabida a 8 entradas analgicas, que estn ubicados en los pines de los puertos A y E (el pin RA4/T0CKI queda excluido). Se convierte solo una entrada analgica a la vez. En el caso de los PIC16F876A y PIC16F873A, que no tienen puerto E, el conversor es el mismo aunque recibe solo los 5 canales analgicos del puerto A. Fuera de eso la compatibilidad es total. Registros del mdulo ADC Los registros que configuran y controlan las operaciones del ADC son cuatro, sin contar con los ya conocidos registros de las interrupciones: ADRESH. Registro del byte alto del resultado de la conversin. ADRESL. Registro del byte bajo del resultado de la conversin. ADCON0. Registro de Control del ADC 0. ADCON1. Registro de Control del ADC 1. ADCS1 ADFM ADCS0 ADCS2 CHS2 CHS1 CHS0 GO/DONE --ADON ----PCFG3 PCFG2 PCFG1 PCFG0

ADCON0 ADCON1

La siguiente secuencia seala los pasos a seguir para realizar una conversin a la vez que devela la funcionalidad de todos los bits de los registros ADCON0 y ADCON1: (Los primeros 4 pasos no van necesariamente en ese orden.)

CURSO_MICROS

P g i n a | 259

Configurar los puertos: los pines analgicos, los pines de referencia y los pines de E/S digital, con los bits PCFG3:PCFG0. Seleccionar el reloj del conversor ADC, con los bits ADCS2:ADCS0. Seleccionar el canal de entrada del conversor ADC, con los bit CHS2:CHS0. Encender el mdulo ADC, seteando el bit ADON. Esperar el tiempo de adquisicin adecuado. Iniciar la conversin, seteando el bit GO/DONE. Esperar a que termine la conversin. Cuando esto pase el bit GO/DONE se limpiar automticamente y el flag ADIF (del registro PIR1) se setear. Leer el resultado de la conversin del par de registros ADRESH:ADRESL.

Configuracin de los canales del ADC

Configuracin de puertos del ADC. CHS2: CHS1: CHS0 Canal 000 Canal 0 ( RA0/AN0 ) 001 Canal 1 ( RA1/AN1 ) 010 Canal 2 ( RA2/AN2 ) 011 Canal 3 ( RA3/AN3 ) 100 Canal 4 ( RA5/AN4 ) 101 Canal 5 ( RE0/AN5 ) 110 Canal 6 ( RE1/AN6 ) 111 Canal 7 ( RE2/AN7 ) Seleccin del canal de conversin del ADC.

CURSO_MICROS

P g i n a | 260

Control del conversor ADC Reloj del ADC y Tiempo de Conversin Como todo circuito sncrono, el conversor ADC necesita de una seal de reloj para dirigir los pasos de su algoritmo de aproximaciones sucesivas. Este reloj puede derivar del oscilador del sistema Fosc o de un oscilador RC interno que tiene el mdulo ADC. La mxima frecuencia del reloj para garantizar una buena conversin debe ser de 625 kHz. El oscilador interno RC es independiente de Fosc y tiene una frecuencia de entre 500kHz y 167 kHz (tpicamente 250 kHz). As que si se le pone como reloj del ADC, el requerimiento estar cubierto. No obstante, el datasheet dice que si se usa un XTAL de ms de 1MHz, su uso solo ser recomendable en modo Sleep. Esto se usa con las interrupciones y lo detallamos al final. Por otro lado, si preferimos que el reloj del ADC derive de Fosc, an es posible aplicarle un divisor de frecuencia y as disminuir su valor. Los factores de divisin se establecen por los bits ADCS2:ADCS0, de acuerdo con la siguiente tabla. ADCS2: ADCS1: ADCS0 000 100 001 101 010 110 11 Frecuencia de reloj del ADC Fosc / 2 Fosc / 4 Fosc / 8 Fosc / 16 Fosc / 32 Fosc / 64 Oscilador Interno RC

Fuentes de reloj del ADC del PIC16F87xA A modo de ejemplo y como avance de las posteriores prcticas veamos qu valores de ADCS1, ADCS1 y ADCS0 podramos usar. Suponiendo que trabajamos con nuestro acostumbrado XTAL (Fosc) de 4MHz los valores de ADCS2:ADCS0 viables son: 001, que da un reloj de Fosc/8 = 500 kHz 101, que da un reloj de Fosc/16 = 250 kHz 010, que da un reloj de Fosc/32 = 125 kHz 110, que da un reloj de Fosc/64 = 67.25 kHz

Los otros valores de ADCS2:ADCS0 producen frecuencias superiores a 625 kHz, las cuales no estn permitidas. El tiempo que demora una conversin para el ADC del PIC es de 12 periodos del reloj. Por ejemplo, si optamos por trabajar con el reloj de 500 kHz, cada conversin tomar 12(1/500k) = 24us. Si obtuviramos el mximo reloj (625 kHz), cada conversin durara

CURSO_MICROS

P g i n a | 261

19.2us. Este conversor es bastante rpido, lo suficiente como para muestrear los sonidos que percibimos. El resultado de la conversin El resultado de la conversin es una cantidad binaria de 10 bits que se deposita entre los registros ADRESH y ADRESL, segn la justificacin mostrada en la siguiente figura y de acuerdo con el bit ADFM (A/D Result Format) del registro ADCON1. El bit ADFM establece el formato del resultado de la conversin.

Justificacin del resultado entre los registros ADRESH y ADRESL. Tiempo de adquisicin El conversor no capta la seal a convertir directamente del pin ANx, sino que primero espera que dicho nivel de tensin se deposite en el capacitor de muestreo Chold, para luego iniciar la conversin desde all. El tiempo que demora este capacitor en cargarse se denomina Tiempo de Adquisicin. ste vara principalmente de acuerdo con la impedancia Rs del circuito externo al canal del ADC. En la documentacin de Microchip se proveen algunas frmulas para calcular el Tiempo de Adquisicin. Pero para quienes no desean tanta matemtica, dice que de no superar la mxima impedancia externa Rs recomendada de 2.5 k, un tiempo de 15 us ser suficiente. Los parmetros pueden cambiar para otros PICs.

CURSO_MICROS

P g i n a | 262

Circuito de entrada del conversor ADC del PIC16F87xA. Programacion del conversor ADC Prctica 1 Uso del conversor ADC Se convierte los valores de hasta 5 potencimetros y se visualiza en la consola RS232. El canal a leer se selecciona por medio del teclado. El circuito

CURSO_MICROS

P g i n a | 263

El cdigo fuente

//////////////////////////////////////////////////////////////////////////////// // FileName: acdtest.c // Processor: PIC16F87xA // Compiler: BoostC // Author: Shawn Johnson // Purpose: Uso del conversor ADC //////////////////////////////////////////////////////////////////////////////// #include <system.h> #include <stdlib.h> // Contiene funciones itoa, atoi,... #include usart.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 void adc_setup(void); unsigned int adc_read(char channel); void main() { char c, buff[10]; unsigned int n; usart_init(); // 9600 - 8N1 puts(\n\r Test del ADC \n\r); adc_setup(); puts(\n\r Ingrese un canal [0..4] \n\r); while(1) { if(kbhit()) { c = getch(); if((c <= 4) && (c >= 0)) { n = adc_read(c-0); itoa(n, buff, 10); // Convertir n en texto decimal puts(\n\r Canal AN); putc; puts( = ); puts(buff); // Mostrar resultado } } } } //**************************************************************************** // Configurar el conversor ADC //**************************************************************************** void adc_setup(void) { // Pines analgicos = Todo PORTA (menos RA4)

CURSO_MICROS

P g i n a | 264

// Pines digitales = Todo PORTE // Vref+ y Vref- = VDD y VSS // Reloj ADC = Fosc/8 = 500 kHz // Estado conversor = ON // Resultado justificado a la derecha // adcon1 = 0b10000010; adcon0 = 0b01000001; } //**************************************************************************** // Lee el canal channel del conversor ADC //**************************************************************************** unsigned int adc_read(char channel) { unsigned int val; adcon0 &= 0xC7; // channel <<= 3; // adcon0 |= channel; // Seleccionar canal delay_us(15); adcon0.GO = 1; // Tiempo de adquisicin // Iniciar conversin

while(adcon0.GO) // Esperar a que continue; // termine la conversin val = adresh; val <<= 8; val |= adresl; return val; } // // // Leer resultado de conversin

Descripcin del programa ? Interrupcin del ADC y conversiones en modo Sleep El ADC tiene un oscilador interno RC con el que puede trabajar incluso en modo Sleep dado que es independiente de Fosc. El oscilador RC ofrece una frecuencia tpica de 250 kHz, que es lo suficientemente baja como para garantizar buenas conversiones. Aunque normalmente se piensa en el modo Sleep como una forma de ahorrar energa, el principal objeto de usar el ADC en este estado es tomar la seal analgica sin presencia del ruido de conmutacin ihnerente de los otros componentes del microcontrolador. El evento que puede disparar la interrupcin del ADC es la conclusin de una conversin. En ese instante, al mismo tiempo que se limpia el bit GO/DONE, se activar al flag ADIF. El bit ADIF se debe limpiar por software.

CURSO_MICROS

P g i n a | 265

La interrupcin del ADC se habilita seteando los bits ADIE, PEIE y GIE. Al setear solo los dos primeros, la interrupcin despertar al PIC pero no se ejecutar la funcin de interrupcin. INTCON GIE PEIE PIE1 PSPIE ADIE PIR1 PSPIF ADIF TMR0IE RCIE RCIF INTE TXIE TXIF RBIE SSPIE SSPIF TMR0IF INTF RBIF CCP1IE TMR2IE TMR1IE CCP1IF TMR2IF TMR1IF

Recordemos que cuando el PIC sale del modo Sleep lo primero que hace es ejecutar la instruccin de ensamblador inmediata a sleep, en seguida comprueba el bit GIE y si est seteado pasa recin a ejecutar la funcin de interrupcin. Lo que significa lo antes dicho es que no ser necesario escribir cdigo de interrupcin. Si habilitamos la interrupcin a medias, haremos que el PIC despierte sin que necesariamente pase a la funcin de interrupcin. Prctica 2 Conversiones en modo Sleep Cada segundo se lee el valor de un potencimetro. La conversin se realiza en modo Sleep. Aunque no era necesario, solo para variar un poco o para simplificar el cdigo esta vez el nico pin configurado como canal analgico ser RA0/AN0.

El circuito Puede ser el de la anterior prctica.

CURSO_MICROS

P g i n a | 266

El cdigo fuente

//////////////////////////////////////////////////////////////////////////////// // FileName: acdsleep.c // Processor: PIC16F87xA // Compiler: BoostC // Author: Shawn Johnson // Purpose: Uso del conversor ADC en modo Sleep //////////////////////////////////////////////////////////////////////////////// #include <system.h> #include <stdlib.h> // Contiene funciones itoa, atoi,... #include usart.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 void adc_setup(void); unsigned int adc_read(char channel); void main() { char buff[10]; unsigned int n; usart_init(); // 9600 - 8N1 puts(\n\r Conversiones en modo Sleep \n\r); adc_setup(); while(1) { n = adc_read(0); // Leer canal AN0 itoa(n, buff, 10); // Convertir n en texto decimal puts(\n\r ); puts(buff); // Mostrar resultado delay_s(1); } } //**************************************************************************** // Configurar el conversor ADC //**************************************************************************** void adc_setup(void) { // Pines analgicos = RA0/AN0 // Pines E/S digital = Todos menos RA0 // Vref+ y Vref- = VDD y VSS // Fuente de reloj = RC // Estado conversor = ON // Resultado justificado a la derecha // adcon1 = 0b10001110; adcon0 = 0b11000001; }

CURSO_MICROS

P g i n a | 267

//**************************************************************************** // Lee el canal channel del conversor ADC //**************************************************************************** unsigned int adc_read(char channel) { unsigned int val; adcon0 &= 0xC7; // channel <<= 3; // adcon0 |= channel; // Seleccionar canal delay_us(15); pie1.ADIE = 1; intcon.PEIE = 1; adcon0.GO = 1; sleep(); pir1.ADIF = 0; pie1.ADIE = 0; intcon.PEIE = 0; val = adresh; val <<= 8; val |= adresl; return val; } // Tiempo de adquisicin // Habilitar // interrupcin del ADC // Iniciar conversin // Entrar en modo Sleep // Limpiar flag // Inhabilitar // interrupcin del ADC // // // Leer resultado de conversin

Descripcin del programa Al despertar el PIC lo primero que hace es ejecutar pir1.ADIF = 0; Como en este programa GIE est siempre en 0, no habr llamada a la funcin interrupt, que tampoco est. Los mdulos CCPx en modo PWM > PWM en los PIC Introduccin No voy a mencionar la utilidad que tienen las seales con Modulacin por Ancho de Pulso PWM en el control de dispositivos como los motores DC. Realizar un PWM software es una labor que consume demasiados ciclos de CPU y no siempre se alcanza la frecuencia deseada. Podemos sentirnos aliviados al saber que los PICmicros tienen incorporados los mdulos CCP1 y CCP2. Cada uno de estos perifricos puede trabajar en tres mdos: Modo de Compacin Modo de Captura Modo PWM

CURSO_MICROS

P g i n a | 268

En lo referente a los modos Captura o Comparacin algunas tpicas aplicaciones son la generacin de un pulso de ancho programado o medida del ancho de un pulso externo. En ambos casos los mdulos CCP1 y CCP2 trabajan con el Timer1. Nuestro inters por ahora es el estudio del modo PWM. El modo PWM tiene una operacin y un control que son compatibles en todos los PIC16 con mdulos CCPx y se conoce como Standard PWM. Los PWM en los PIC18 son ms potentes y variados, destacando el llamado Enhanced PWM. Con todo, en muchos casos todava cuentan con el Standard y en otros, un modo compatible con l. Cada mdulo CCPx puede generar una onda PWM de hasta 10 bits de resolucin con una frecuencia y duty cycle configurables. Ambas ondas son semi independientes, esto es, pueden tener diferente duty cycle pero comparten la misma frecuencia. Cada CCPx se puede habilitar o inhabilitar independientemente. La salida PWM del mdulo CCP1 es el pin RC2/CCP1 y del mdulo CCP2 es el pin RC1/CCP2. Dichos pines deben ser configurados como salidas. Registros de los mdulos CCPx Los registros de duty cycle presentados en seguida tienen diferente funcin en los modos Captura o Comparacin. El Timer2 se ala con los modulos CCPx solo en modo PWM. CCP1CON y CCP2CON. Registros para la configuracin del los mdulos CCP1 y CCP2 respectivamente en modo Captura, Comparacin o PWM. Son gemelos. CCPR1L y CCPR2L. Registros para controlar los duty cycle de cada onda PWM. Son gemelos. CCPR1H y CCPR2H. Funcionan como registros imagen de los dos anteriores. No deben se accedidos directamente. TMR2, PR2 y T2CON. Son los conocidos registros del Timer2. Sirven para establecer el periodo, o la frecuencia, de los dos canales PWM. El Timer2 no pierde ninguna de sus capacidades que conocemos, o sea que podemos seguir temporizando, utilizando sus interrupciones, etc.

Dado que en modo PWM ambos mdulos CCPx son completamente indnticos en control y configuracin, estara de ms describir cada mdulo por separado o al mismo tiempo. Bastar con discutir la operacin de CCP1 y se entender que lo mismo es vlido para CCP2. Empecemos entonces: para configurar el mdulo CCP1 en modo PWM, podemos escribir 0x0C en el registro CCP1CON. Eso es todo respecto a la configuracin. En lo que resta de este captulo estaremos hablando casi en exclusiva del control del duty cycle de la onda.

CURSO_MICROS

P g i n a | 269

ste es el mapa de bits del registro CCP1CON. --- --- CCP1X CCP1Y CCP1M3 CCP1M2 CCP1M1 CCP1M0 Bit7 Bit 0

CCP1X:

PWM Least Significant bit Vlidos solo en modo PWM:

CCP1Y:

Estos son los dos bits menos significativos del Duty Cycle del PWM Los ocho bits ms significativos se encuentran en el registro CCPR1L

CCP1M3: CCP1 Mode Select bits CCP1M0: 0000 = Modo Captura/Comparacin/PWM inhabilitados (resetear mdulo CCP1) 0100 = Modo Captura, cada flanco de bajada 0101 = Modo Captura, cada flanco de subida 0110 = Modo Captura, cada 4 flancos de subida 0111 = Modo Captura, cada 16 flancos de subida 1000 = Modo Comparacin, setear salida en coincidencia (el bit CCP1IF se setea) 1001 = Modo Comparacin, limpiar salida en coincidencia (bit CCP1IF se setea) 1010 = Modo Comparacin, generar interrupcin software en coincidencia (el bit CCP1IF se setea, el pin de CCP1 no se afecta) 1011 = Modo Comparacin, disparar evento especial (el bit CCP1IF se setea, el pin de CCP1 no se afecta); CCP1 resetea TMR1; CCP2 resetea TMR1 e inicia una conversion del ADC (si el ADC est habilitado) 11xx = Modo PWM

Generacin de ondas PWM de 8 bits Los mdulos CCPx pueden generar ondas PWM de hasta 10 bits de resolucin. En el enfoque seguido en este captulo se estudia el mdulo CCP1 asumiendo primero que genera ondas PWM de 8 bits. Esto se consigue cortando los dos bits menos significativos. Luego se extendiende la operacin a los 10 bits completos.

CURSO_MICROS

P g i n a | 270

Los dos mdulos CCPx emplean el Timer2 como contador principal. El Timer2 funciona de la forma que conocemos: su registro TMR2 corre libremente desde 0x00 hasta el valor del registro PR2 y despus de la coincidencia se resetea para volver a contar desde 0x00. Ese tiempo cclico ser el perodo de la onda PWM. Para establecer el duty cycle se emplea el registro CCPR1L del siguiente modo: cuando TMR2 sea igual a CCPR1L el pin RC2/CCP1 se limpiar y cuando TMR2 alcance el valor de PR2, el pin RC2/CCP1 se setear en el prximo ciclo. Estas operaciones estn graficadas en la siguiente figura:

Generacin de ondas PWM del mdulo CCP1. Periodo y frecuencia y de la onda PWM Ya vimos que el periodo de la onda PWM est determinado por el tiempo que dura el conteo del TMR2 desde 0 hasta el valor del registro PR2. Esto es:

Donde: PR2 = Valor del registro PR2 (entre 0 y 255). Fosc = Frecuencia del XTAL. Prescaler = Prescaler del Timer2 (1, 4 16). Se configura en el registro T2CON.

Recordemos que el Timer2 tambin tiene un Postscaler, pero es una etapa posterior y no interviene directamente en el avance del Timer2. En la prctica es mucho ms habitual hablar de la frecuencia del PWM. Como sta es la inversa del periodo, de la frmula anterior surge la siguiente:

CURSO_MICROS

P g i n a | 271

Duty cycle de la onda PWM El duty cycle es la cantidad de tiempo que en un periodo la salida PWM permanece en estado alto. Este tiempo queda determinado por el valor del registro CCPR1L. Mientras TMR2 sea menor que CCPR1L la salida PWM ser un 1 lgico. Apenas TMR2 alcance a CCPR1L, la salida ser 0. En caso de que este tope sea superior al periodo, la salida ya no conmutar a 0. En consecuencia, el duty cycle se controla variando el valor del registro CCPR1L. La frmula que da su valor en tiempo es:

Donde: CCPR1L = Valor del registro CCPR1L (entre 0 y 255). Fosc = Frecuencia del XTAL. Prescaler = Prescaler del Timer2 (1, 4 16).

Tambin es mucho ms ilustrativo hablar del duty cycle en trminos de porcentajes. Un 0% significa que la salida es siempre bajo, 100% es un alto continuo, un 50% significa una onda cuadrada simtrica, y as. Para esto solo hay que dividir el duty cycle (en tiempo) entre el periodo y multiplicarlo por 100%. Realizando esta operacin con las frmulas presentadas anteriormente llegaremos a:

Esta frmula es fcil de interpretar: el numerador CCPR1L representa al duty cycle y el denominador PR2+1, al periodo. La siguiente figura indica que cada periodo se divide en PR2+1 tramos equitativos y que CCPR1L es la cantidad de tramos que estn en alto. Eso es una PWM.

Duty cycle de la onda PWM.

CURSO_MICROS

P g i n a | 272

Prctica 1 PWM de 8 bits Se generar una onda PWM de 10 kHz. El Duty cycle se incrementa o decrementa con las teclas + de la consola RS232. El circuito

El cdigo fuente

//////////////////////////////////////////////////////////////////////////////// // File Name: CCP1_PWM.c // Processor: PIC16F87xA // Compiler: BoostC/C++ // Author: Shawn Johnson // Purpose: Mdulo CCP1 en modo PWM //////////////////////////////////////////////////////////////////////////////// #include <system.h> #include usart.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 void main(void) { usart_init(); // 9600 - 8N1 puts(\n\r CCP1 en modo PWM); puts(\n\r Cambie Duty cycle con +/-);

CURSO_MICROS

P g i n a | 273

ccp1con = 0x0C; // CCP1 en modo PWM t2con = 0x04; // Prescaler = 1, Timer2 = ON pr2 = 99; // Periodo PWM = 100us => Freq = 10 kHz ccpr1l = 0; // Duty cycle inicial = 0 = 0% trisc.2 = 0; // pin RC2/CCP1 salida de PWM 1 while(1) { if(kbhit()) { char c = getc(); if(c == +) { if(ccpr1l <= pr2) ccpr1l++; } else if(c == -) { if(ccpr1l) ccpr1l--; } } } } Descripcin del cdigo En el programa se genera la onda PWM con el mdulo CCP1 pero bien pudo ser con el CCP2 o tambin se pudo tener dos PWM al mismo tiempo Generacin de ondas PWM de 10 bits En esta seccin vamos a extender la resolucin de nuestro PWM a 10 bits. Partamos por el hecho de que la frecuencia (y periodo) de esta seal ser la misma que se puede conseguir cuando se trabaja con 8 bits. Lo que vara es la finura del ducy cycle. Con 10 bits el duty cycle podr recorrer todo el periodo dividindolo hasta en 210 = 1024 tramos.iguales. Para que el duty cycle sea de 10 bits el registro CCPR1L se acopla con los bits 4 y 5 del registro CCP1CON. Por su parte, para que el Timer2 pueda desplazarse por cada uno de los 1024 tramos tendr que juntarse con los dos bits de menor peso del oscilador del sistema Fosc. El PWM de 10 bits no requiere de ninguna configuracion adicional. De hecho los modulos CCPx siempre operan asi. Es solo que nosotros hemos estado trabajando con los 8 bits mas altos.

Donde:

CURSO_MICROS

P g i n a | 274

Fosc = Frecuencia del XTAL. Prescaler = Prescaler del Timer2 (1, 4 16). CCPR1L:CCPICON<5:4> = Valor del registro CCPR1L concatenado con los bits 5 y 4 del registro CCP1CON (tericamente entre 0 y 1023).

Por ltimo, vamos a interpretar la frmula del duty cycle expresada en porcentaje. sta se obtiene dividiendo la frmula del duty cycle (en tiempo) entre la frmula de periodo y multiplicndola por 100%. El resultado simplificado sera:

El denominador nos dice que el periodo se dividir en 4(PR2+1) intervalos equitativos. PR2 sigue siendo de 8 bits pero el factor 4 nos dice que la resolucin se amplia en incrementos de 4 tramos. As mismo, el numerador sigue indicando la cantidad de intervalos, de los 4(PR2+1), que estarn en alto. Prctica 2 PWM de 10 bits El PICmicro saca una onda PWM de 10 kHz y duty cycle configurable por el teclado de la consola RS232 con resolucin de 0.25%. El circuito Se puede usar el circuito de la anterior prctica.

CURSO_MICROS

P g i n a | 275

El cdigo fuente //////////////////////////////////////////////////////////////////////////////// // File Name: pwm10.c // Processor: PIC16F87xA // Compiler: BoostC/C++ // Author: Shawn Johnson // Purpose: Mdulo CCP1 en modo PWM con resolucin de 10 bits //////////////////////////////////////////////////////////////////////////////// #include <system.h> #include <ctype.h> // Contiene funciones isdigit,... #include <stdlib.h> // Contiene funciones atoi,... #include usart.h #pragma DATA _CONFIG, _XT_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF & _WRT_HALF #pragma CLOCK_FREQ 4000000 void SetDuty1(unsigned int duty); void GetStr(char * buffer, unsigned char len); void main(void) { char buf[10]; unsigned int n; usart_init(); // 9600 - 8N1

ccp1con = 0x0C; // CCP1 en modo PWM t2con = 0x04; // Prescaler = 1, Timer2 = ON pr2 = 99; // Periodo PWM = 100us => Freq = 10 kHz SetDuty1(0); // Duty cycle inicial = 0 = 0% trisc.2 = 0; // pin RC2/CCP1 salida de PWM 1 puts(\n\r PWM 1 - 10 kHz - 10 bits); puts(\n\r Ingrese Duty cycle [0..400] \n\r); while(1) { GetStr(buf, 3); // Leer texto de 3 nmeros n = atoui_dec(buf); // Convertir texto numrico en nmero SetDuty1(n); } } //**************************************************************************** // Pone Duty cycle del PWM del mdulo CCP1. Resolucin = 10 bits //**************************************************************************** void SetDuty1(unsigned int duty) { ccpr1l = duty>>2; ccp1con.5 = duty.1? 1:0; ccp1con.4 = duty.0? 1:0; }

CURSO_MICROS

P g i n a | 276

//**************************************************************************** // Lee una cadena de texto de len nmeros // El tamao de buffer debe ser mayor que len. //**************************************************************************** void GetStr(char * buffer, unsigned char len) { char c; unsigned char i=0; while(1) { // Bucle incondicional c = getc(); if(isdigit&&(i<len)) // Si c es ... { buffer[i++] = c; // Guardar en buffer putc; // Eco } else if((c==\b)&&(i)) // Si c es RETROCESO y si i>0 { i--; // putc; // Eco } else if((c==\r)&&(i)) // Si c es ENTER y si i>0 { buffer[i] = \0; // Poner un 0x00 (fin de cadena) putc; // Eco break; // Salir del bucle } } } Descripcin del programa El cdigo no pone restricciones en el ingreso del duty cycle. Eso lo hice a propsito para examinar cmo responde el mdulo PWM a valores de duty cycle fuera del rango tericamente permitido.

CURSO_MICROS

P g i n a | 277

Programador de microcontroladores PIC TE-20 Archivos para contruir un programador TE20


El circuito elctrico Esquema PCB Esquema PCB invertido Esquema de componentes Esquema de puentes

Prcticas del mdulo 1 Aqu no se aprecian muchas prcticas porque gran parte de este mdulo se enfoca al manejo de las herramientas bsicas de desarrollo de proyectos con microcontroladores, como la grabacin de PICs, uso del IDE MPLAB o uso del simulador Proteus VSM.

2-1 Grabacin de microcontroladores PIC 4-1 Uso de puertos 4-2 Uso de puertos como entradas 4-3 Uso de puertos como salidas 8-1 Uso de puertos Contador Johnson 8-2 Filtros antirrebote software 8-3 Tablas de bsqueda 8-4 Tablas de bsqueda 8-5 Display 7 segmentos 8-6 Retrazo de repeticin - velocidad de repeticin

Prcticas del mdulo 2


BoostC 1: Uso de puertos y delays BoostC 2: Operadores, sentencia if, bucle while BoostC 3: Sentencia if-else, arrays constantes en BoostC BoostC 4: Bucle de iteracin for LCD 1: Visualizacin de texto LCD 2: Visualizacin de nmeros LCD 3: Creacin de caracteres personalizados

CURSO_MICROS

P g i n a | 278

Keypad 1: Lectura de teclado por sondeo Keypad 2: Lectura de teclado por interrupciones Interr 1: Uso de la interrupcin RB0/INT Interr 2: Uso de la interrupcin RB0/INT + Modo Sleep Interr 3: Uso de la interrupcin de Cambio de PORTB Interr 4: Uso de la interrupciones mltiples Interr 5: Interrupciones en ensamblador Timer0 1: El Timer0 en modo Temporizador Timer0 2: Uso de la Interrupcin del Timer0 Timer0 3: Temporizaciones grandes Timer0 4: El Timer0 en modo Contador Timer1 1: El Timer1 en modo Temporizador Timer1 2: El Timer1 en modo Contador Timer1 3: El Timer1 con XTAL externo Timer2 1: Temporizaciones precisas con el Timer2 USART 1: Interface PC - PIC - LCD USART 2: Manejo de texto y nmeros USART 3: Uso de las interrupciones del USART USART 4: Uso de los buffers circulares USART 5: Funciones RS-232 software: LCD serial RS-232 I2C 1: Uso de la memoria 24xx128 Acceso individual I2C 2: Uso de la memoria 24xx128 Acceso secuencial ADC 1: Uso del ADC con resolucin de 10 bits ADC 2: Interrupcin del ADC y conversiones en modo Sleep PWM 1: Modulacin PWM con resolucin de 8 bits PWM 2: Modulacin PWM con resolucin de 10 bits

Misc 1: Uso del Watchdog Misc 2: Uso del Watchdog + Sleep

CURSO_MICROS

P g i n a | 279

Misc 3: Uso de la EEPROM interna Misc 4: Uso de la Memoria FLASH interna Misc 5: Funciones Reentrantes

Prcticas de control de dispositivos I2C


ee-1 Uso de las memorias EEPROM 24XXX ee-2 Uso de las EEPROM 24XXX con sondeo del bit Acknowledge ee-3 Uso de las EEPROM 24XXX con accesos secuenciales ee-4 Uso de las EEPROM 24XXX pequeas ee-5 Deteccin automtica de EEPROMs 24XXX grandes y pequeas rtc-1 Uso del DS1307: Reloj de Tiempo Real rtc-2 Uso del DS1340: Reloj de Tiempo Real con Cargador rtc-3 Uso del DS3232: RTC de gran precisin y con cristal incorporado rtc-4 Uso del PCF8563: Reloj de Tiempo Real th-1 Uso del DS1621: Sensor de temperatura de 9 bits th-2 Uso del DS1621: Lectura de temperatura completa th-3 Uso del DS1621: Temperatura con precisin manual th-4 Uso del DS1624: Sensor de temperatura de 13 bits th-5 Uso del LM75: Sensor de temperatura de 9 bits th-6 Uso del LM76: Sensor de temperatura de 12 bits th-7 Uso del LM73: Sensor de temperatura de 14 bits th-8 Uso del TC74: Sensor de temperatura de 8 bits th-8 Uso del TMP100: Sensor de temperatura de 12 bits ex-1 Uso del expansor de E/S PCA9554/PCA9554A. Control de teclado ex-2 Uso del expansor de E/S PCF8574/PCF8574A. Control de LCD ex-3 Uso del expansor de E/S PCF8574/PCF8574A. Control de teclado

You might also like