microcontroladores pic

64
Voy a contar en muy poco tiempo el proceso de auto-aprendizaje que yo he seguido de los microcontroladores PIC, con un enfoque eminentemente práctico que permita a cualquiera entender las características principales de los microcontroladores PIC de gama media y mejorada, y eventualmente usar un microcontrolador para cualquier diseño de complejidad pequeña o media. Asimismo esta introducción puede considerarse el primer paso para conocer en profundidad el amplísimo mundo de los controladores de una forma rápida; desde aquí se podrá profundizar con la información que provee el fabricante sobre el uso de funcionalidades más avanzadas o con cualquier otra gama de microcontroladores, según sean las necesidades del interesado. Dada la cantidad de información y el carácter práctico de esta introducción, se recomienda una vez concluida ésta, que la persona interesada instale los paquetes software que se usan para programar los PIC por sí mismo, y también que se trate de hacer programas similares a los que se van a presentar con pequeñas variaciones, ya que la práctica hace que se afiancen mejor los conocimientos. A mi Padre. Aún con el dolor de perderte siempre recordaré tu alegría Papá. Tu hijo 1

Upload: sohar-carr

Post on 11-May-2015

12.284 views

Category:

Education


22 download

DESCRIPTION

Introducción a los Microcontroladores PIC

TRANSCRIPT

Page 1: Microcontroladores PIC

Voy a contar en muy poco tiempo el proceso de auto-aprendizaje que yo he seguido de los microcontroladores PIC, con un enfoque eminentemente práctico que permita a cualquiera entender las características principales de los microcontroladores PIC de gama media y mejorada, y eventualmente usar un microcontrolador para cualquier diseño de complejidad pequeña o media.Asimismo esta introducción puede considerarse el primer paso para conocer en profundidad el amplísimo mundo de los controladores de una forma rápida; desde aquí se podrá profundizar con la información que provee el fabricante sobre el uso de funcionalidades más avanzadas o con cualquier otra gama de microcontroladores, según sean las necesidades del interesado.Dada la cantidad de información y el carácter práctico de esta introducción, se recomienda una vez concluida ésta, que la persona interesada instale los paquetes software que se usan para programar los PIC por sí mismo, y también que se trate de hacer programas similares a los que se van a presentar con pequeñas variaciones, ya que la práctica hace que se afiancen mejor los conocimientos.

A mi Padre. Aún con el dolor de perderte siempre recordaré tu alegría Papá.Tu hijo

1

Page 2: Microcontroladores PIC

03.- ¿ Qué es un microcontrolador ? 04.- Familias de microcontroladores

05.- Gamas de los PIC de 8 bits 06.- Características de los PIC

07.- Periféricos y auto-control 08.- PIC16F690 en BLOQUES

09.- MAPA DE MEMORIA 10.- STATUS

11.- MPLAB IDE y PICkit2 12.- Placa de desarrollo ++

13.- Nuestro primer programa (ASM) 14.- INSTRUCCIONES PARA BITS

15.- INSTRUCCIONES PARA BYTES 16.- INSTRUCCIONES CON LITERAL Y DE CONTROL

17.- TABLA DE INSTRUCCIONES 18.- Macros

19.- PROGRAMA BLINK.ASM 20.- PROGRAMA ROTATE.ASM

21.- Puertos polivalentes 22.- Diagrama del A/D

23.- Selección del canal A/D 24.- Configurar el A/D

25.- Programa A/D A2D.ASM 26.- Rebotes de tecla

27.- Eliminación de rebotes 28.- Subrutina Delay

29.- OPTION_REG 30.- Interrupciones

31.- INTCON - INTERRUPT CONTROL REGISTER 32.- Timer0

33.- INTERRUPT.ASM 34.- FILTER.ASM

35.- GREYCODE.ASM 36.- 1DIGITO.ASM

37.- 2DIGITOS.ASM 38.- 7SEGMENT.ASM

39.- 7SEG_INTERR.ASM 40.- PWM.ASM

41.- Configuración 42.- ‘C’ Led_blink.c

43.- ‘C’ básico 1 44.- ‘C’ básico 2

45.- Display7seg_int.c 46.- Fichero de listado.lst

47.- Humid.c 48.- LCDs

49.- LCD_rutinas_16F690_PORT-C.c - 1 50.- LCD_rutinas_16F690_PORT-C.c - 2

51.- Humid_LCD.c 52.- PWM_LCD.c

53.- LCD_EasyPIC.c 54.- Timer1_Cal_quartz.c PIC18F1320

55.- Debugging: Teclado.c 56.- Watchdog: Test_WDT.c

57.- Encoder_check.c 58.- Photodiode_PCB_controller.c

59.- P18F2420_ADconverter.c 1 60.- P18F2420_ADconverter.c 2

61.- P18F2420_ADconverter.c 3 62.- P18F2420_ADconverter.c 4

63.- P18F2420_ADconverter.c 5 64.- P18F2420_ADconverter_callertable.txt

2

Page 3: Microcontroladores PIC

Un controlador es un dispositivo que se emplea para el gobierno y/o la monitorización de uno o varios procesos. Hace unas décadas, los controladores se construían exclusivamente con componentes de lógica discreta, posteriormente se emplearon los microprocesadores, que se rodeaban con chips de memoria y E/S sobre una tarjeta de circuito impreso. En la actualidad, todos los elementos del controlador se han podido incluir en un chip, el cual recibe el nombre de microcontrolador.El microcontrolador es un sencillo pero completo computador contenido en el corazón (chip) de un circuito integrado. Un microcontrolador es un circuito integrado de alta escala de integración que incorpora la mayor parte de los elementos que configuran un controlador.

Un microcontrolador dispone normalmente de los siguientes componentes:

· Procesador o CPU (Unidad Central de Proceso).· Memoria para el programa tipo ROM/PROM/EPROM o FLASH.· Memoria RAM y EEPROM para contener los datos.· Líneas de E/S digitales para comunicarse con el exterior (puertos).· Generador de impulsos de reloj que sincronizan el funcionamiento de todo el sistema.· Osciladores, contadores y temporizadores.· Diversos módulos para el control de periféricos (puertos serie y paralelo, conversores Analógico/Digital y Digital/Analógico, etc.).

3

Page 4: Microcontroladores PIC

Hoy en día prácticamente todos los microcontroladores se fabrican con tecnología CMOS (Complementary Metal Oxide Semiconductor) por su alta capacidad de integración, su bajo consumo y su alta inmunidad al ruido.

Entre los más conocidos podemos citar:

• 8048 de Intel. Es el ‘padre’ de los microcontroladores actuales.• 8051 de Intel. Es antiguo (~1980) muy popular ya que es producido por varios fabricantes, y su arquitectura se sigue usando para productos nuevos, por ejemplo por ATMEL.• 68HC11 de Motorola y Toshiba, precursor de la familia 68xxx de Motorola mucho más potente.• PIC de MicroChip que posee una amplísima gama. Fueron los primeros microcontroladores con arquitectura RISC (Reduced Instruction Set Computer). Trataremos de éstos a lo largo de esta presentación.• AVR de ATMEL, nombre genérico de otra amplia gama de microcontroladores muy potentes y rápidos: AT90Sxx, ATtinyxx, ATmegaxx. Todos ellos pueden trabajar en C con herramientas libres.

La mayoría de éstos microcontroladores tienen una arquitectura de buses Von Newman (o Princeton) y conjunto de instrucciones grande (CISC – Complex Instruction Set Computer); frente a los PIC’s que poseen una arquitectura de buses Harvard y un conjunto reducido de instrucciones (RISC). La arquitectura Von Newman (muy común en casi todos los procesadores como los PC’s) tanto las instrucciones del programa como los datos llegan a la CPU a través de un solo bus, mientras que en la arquitectura Harvard hay dos buses, uno para las instrucciones de programa y otro para los datos; esto permite tener distinto número de líneas (ancho del bus) para las instrucciones del que tiene el bus de datos; en el caso de los PICs el bus de instrucciones tiene 12, 14 ó 16 bits y el de datos es de 8 bits; dado que la memoria de programa (FLASH-ROM) está integrada en el chip no existe acceso al bus de instrucciones desde el exterior, la interacción con el mundo se hace a través del bus de datos.Programar los PIC en ensamblador es algo más fácil que para otros tipos porque sólo tienen 35 instrucciones (RISC) frente a por ejemplo las 130 de los AVR; en cualquiera de los dos casos suele ser más fácil y rápido programar en alto nivel (C o BASIC).

4

Page 5: Microcontroladores PIC

Además de la división según la arquitectura de 8, 16 ó 32 bits, la familia de 8 bits de MicroChip se divide en gamas según la evolución que han tenido los microcontroladores. Esta clasificación es muy dinámica, tanto que actualmente MicroChip habla de tres rangos, en vez de los cinco (históricos) dados en la primera tabla; las gamas enana y baja se han fusionado (baseline), así como las gamas alta y mejorada se integran en la alta (highperformance) aunque realmente la antigua gama alta (PIC17Fxxx) ha desaparecido.Esta tabla es pues inexacta y se menciona sólo por dar información histórica, asimismo dado la rapidísima evolución del mercado de microcontroladores PIC, los números que se indican corresponden a un momento en el tiempo y por tanto varían al irse introduciendo nuevos productos con mayores prestaciones.Los nombres de los PIC’s que se dan corresponden a los microcontroladores que tienen memoria FLASH (de ahí la ‘F’ del nombre), aunque también existen otros identificativos para distintos tipos de memoria PROM que MicroChip llama OTP (One Time Programming) o también con memoria ROM que se crea en el proceso de la fabricación. La gran ventaja de la memoria FLASH es que puede borrarse y regrabarse muchas veces y es por tanto muy adecuada para el desarrollo o para las actualizaciones en el mismo chip físico.En la columna de ‘Instrucciones’ tenemos el tamaño de la palabra de la memoria de programa donde se almacenan las instrucciones que ejecuta el microcontrolador, vemos que para las gamas media y alta las instrucciones tienen respectivamente 14 y 16 bits.Nosotros trabajaremos sólo con dispositivos con memoria FLASH de las gamas media y alta. Usaremos un sistema de desarrollo llamado PicKit2 Low Pin Count Demo Board, con un PIC16F690; mientras no se especifique lo contrario nos referiremos a ese sistema de desarrollo y tipo de PIC. Para los programas en C usaremos también el sistema de desarrollo EASYPIC.Es interesante destacar que la gama PIC18Fxxxx posee un número mayor de instrucciones y un mapa de memoria diferente para permitir la programación de estos dispositivos en C; de hecho MicroChip sólo provee un compilador C para la gama alta; afortunadamente otros vendedores han desarrollado compiladores para todas las gamas, aunque con las lógicas limitaciones debidas al hardware. Nosotros usaremos el compilador de Mikroelektronika(www.mikroe.com) del que tenemos licencias, es fácil de usar, tiene buenas librerías y se puede descargar una versión limitada gratuitamente.

5

Page 6: Microcontroladores PIC

La tecnología CMOS es muy rápida y como no necesita resistencias de polarización, prácticamente sólo consume energía en las transiciones o si tiene que proporcionar corriente a las cargas que pueda tener en el circuito. Puede funcionar con un rango de voltajes de 2 a 5.5 V. Para aplicaciones con baterías el PIC puede detener el reloj (modo sleep) y dado que la RAM es estática el consumo es virtualmente cero en dicho modo.

El procesador RISC con arquitectura Harvard facilita implementar un esquema pipeline, o sea que la lectura de la siguiente instrucción se hace *durante* la ejecución de la instrucción actual lo que permite una mayor velocidad de ejecución en casi todos los casos. Sólo cuando se ejecuta un salto en el programa se tiene que desechar la instrucción leída, por lo que las instrucciones de salto requieren el doble de tiempo que las otras. De cualquier manera cada instrucción necesita 4 ciclos de reloj para ejecutarse, y según hemos visto las de salto requieren 8 ciclos de reloj para su ejecución. Así pues si un PIC tiene un reloj de 8 MHz, como máximo ejecuta a una velocidad de 2 MIPS (Mega Instrucciones por Segundo) y como máximo los PIC de gama alta alcanzan 10 MIPS a 40 MHz; los procesadores de ATMEL ejecutan una instrucción por ciclo de reloj llegando a 20 MIPS.

Los PIC tienen un solo registro llamado W (Working register) conocido también como acumulador, que lógicamente tiene 8 bits. El bus de direcciones de acceso a la memoria es de 7 bits, por lo que sólo se puede acceder a 128 bytes simultáneamente; para los PICs que tienen más memoria esta se organiza en bancos de 128 bytes o menores; para cambiar de banco de memoria hay que ejecutar ciertas instrucciones, lo que introduce cierta complejidad y propensión a tener fallos en los programas. Esta es posiblemente la característica menos ‘agradable’ de los PIC y siempre debe tenerse en cuenta, al menos trabajando en ensamblador; en C el compilador se encarga de ello aunque por un precio en la eficiencia, ya que se hacen muchas operaciones de selección de banco de memoria que no son necesarias. La nomenclatura que usa McroChip para referirse a las celdas de memoria es un tanto confusa ya que a veces se refiere a ellas como ‘registros’ y otras como ‘file’ (nombre ciertamente desafortunado …); los microcontroladores de ATMEL tienen 16 auténticos registros que son independientes de la memoria, siendo éste el concepto más usual de registro.

El control de periféricos se hace simplemente escribiendo en una determinada celda de memoria genéricamente llamada SFR (Special Function Register). Por ejemplo, si quiero poner a nivel lógico alto todas las patillas del puerto A, tendré que escribir FFh en la dirección de memoria 5, ya que esta es la dirección asignada para leer y/o escribir en el puerto A. Por tanto para controlar los dispositivos del PIC se usa exactamente la misma instrucción que para almacenar un dato en memoria, solo que para ciertas posiciones de memoria (SFR’s) estaremos accediendo a un dispositivo físico que nos conecta con el mundo real; esto es lo que significa que los periféricos está mapeados en la memoria.

/******** Continua en la diapositiva siguiente ********/

6

Page 7: Microcontroladores PIC

Este PIC tiene un oscilador interno a 8 MHz de tipo RC, poco estable (+-1%) con un divisor hasta 32 kHz; también puede trabajar con un cuarzo o resonador externo hasta 20 Mhz. El modo de funcionamiento se selecciona en el momento de la programación del dispositivo y no puede cambiarse durante la ejecución (aunque con el oscilador interno se puede seleccionar el factor de división).El PIC posee dos memorias permanentes, la FLASH con palabras de 14 bits contiene el programa grabado en el chip en el momento de la programación o ‘quemado’ (burn) del dispositivo y la EEPROM el la que se puede almacenar datos durante la ejecución. La FLASH permite más de 100.000 escrituras, mientras que la EEPROM más de 1.000.000, aunque por velocidad y durabilidad nunca debe usarse como RAM. La retención de datos en ambos casos es mayor de 40 años.Los comparadores permiten generar una interrupción o cambiar el estado de un SFR (registro en memoria) según el valor de un voltaje analógico sea mayor o menor de una referencia que puede ser interna o externa.Temporizadores: Timer0 de 8 bits con pre-scaler, Timer1 de 16 bits puede trabajar como contador, con pre-scaler y oscilador independiente, y el Timer2 de 8 bits con pre y post-scaler. El módulo ECCP+ (Enhanced Capture Compare) trabaja con los temporizadores para medir tiempos con mucha precisión y también controla un generador de PWM (Pulse Width Modulation) con 10 bits de resolución y 1, 2 ó 4 salidas.EUSART es el Enhanced Universal Synchronous Asynchonous Receiver Transmitter, soporta RS485, RS232, LIN e I2C; es capaz de auto-detectar la velocidad y puede generar una interrupción con el bit de start.El módulo ICSP (In-Circuit Serial Programming) es el que nos permite programar el dispositivo sin necesidad de retirarlo de su PCB (con algunas condiciones). Para algunos PICs también permite hacer debugging en la PCB.

Al entrar en modo sleep se detiene el oscilador, se vuelve a la ejecución después de un reset o una interrupción; cada vez que se re-inicia el PIC el módulo PWRT (Power-up Timer) espera el arranque del reloj antes de empezar la ejecución y el OST (Oscillator Start-up Timer) permite incrementar la espera hasta la estabilización completa del mismo (1024 periodos). El POR (Power-on Reset) genera una señal limpia de reset independientemente de la pendiente de subida de la alimentación; el BOR (Brown-out Reset) generará un reset si la tensión de alimentación baja de un cierto nivel pre-establecido. El módulo watchdog (WDT) es un temporizador totalmente independiente de la ejecución del programa que generará un reset pasado un cierto tiempo, que es configurable, sin que la ejecución de programa haya ‘limpiado’ este temporizador; normalmente se usa para evitar que el procesador se quede ‘colgado’ o ejecutando un bucle sin fin indeseado.

7

Page 8: Microcontroladores PIC

Vemos aquí el diagrama de bloques del PIC16F690 cada salida o entrada está marcada con un cuadrado con una X, y en la diapositiva anterior veíamos también la asignación de los 20 pines del dispositivo. Observamos que tenemos del orden de 50 salidas/entradas, lo que obviamente nos lleva a concluir que no todos los módulos podrán usarse simultáneamente en una determinada aplicación. Así por ejemplo la patilla RA3/MCLR/Vpp puede funcionar como línea 3 del puerto A, o como master clear (reset) o se usa para introducir el voltaje de programación del dispositivo (Vpp) que es de unos 12 voltios. En la configuración del dispositivo tenemos que decidir si esta patilla se usará como puerto general RA3 o como reset, y en cualquier caso la circuitería conectada a esta patilla deberá tener en cuenta que se le puede aplicar un voltaje alto (hasta 13.5 V) si se quiere tener la posibilidad de programar el dispositivo en su placa de aplicación (in-circuit).

De izquierda a derecha y arriba hacia abajo tenemos: la memoria de programa 4k x 14 bits; el contador de programa PC; la pila subrutinas de 8 niveles; el bus de datos de 8 bits; la RAM 256 bytes; los puertos de propósito general A, B y C; el bus de programa de 14 bits; la unidad de procesamiento aritmético/lógico ALU con sus registros asociados; el descodificador de instrucciones; el módulo de control; oscilador interno; la unidad generadora de tiempos; los temporizadores 0, 1 y 2; el módulo de comunicaciones serie asíncronas EUSART; el módulo de captura/comparación mejorado ECCP+; puerto de comunicaciones síncronas SSI; el módulo conversor A/D de 10 bits con 12 entradas y una referencia; módulo de comparadores; y la memoria EEPROM de 256 bytes.

8

Page 9: Microcontroladores PIC

Vemos aquí la memoria RAM organizada en 4 bancos, de 128 bytes cada uno. Los PIC sólo pueden acceder a un banco a la vez. El motivo de estas limitaciones es que los códigos de las instrucciones de ensamblador (conocidos como opcodes) tienen sólo 7 bits para especificar la dirección de la celda de memoria; en los PIC de gama alta cuyas instrucciones son de 16 bits los bancos son de 256 bytes, y pueden tener hasta 16 bancos o sea 3.9 kB de RAM. Todas las celdas con nombre son los llamados SFR (Special Function Registers) que están relacionadas directamente con el hardware; las celdas marcadas en gris son inexistentes para el PCI16F690; si por error se accede a ellas se lee siempre 0 y si se escribe en ellas el hardware lo ignora.Vemos también que el número total de bytes de la RAM es de 96 en banco 0, y 80 en los bancos 1 y 2; lo que nos da un total de 256 bytes para almacenamiento general.El SFR STATUS está presente en todos los bancos y vemos que su dirección (FileAddress) es 3. STATUS es el registro que contiene los bits de estado de la CPU y también 2 bits llamados RP1 y RP0 que son los que nos permiten establecer el banco al que estamos accediendo. (Ver diapositiva siguiente para más detalles).Las posiciones F0 a FF en el banco 1, las 170 a 17F en el banco 2 y las 1F0 a 1FF en el banco 3, están mapeadas a las 16 posiciones 70 a 7F en el banco 0; esto es que sea cual sea el banco que tengamos seleccionado siempre leeremos lo mismo en éstas 16 posiciones del final del banco. Esto es muy útil para almacenar datos que deberán ser accesibles en varias partes del programa y no tendremos que hacer cambios de banco para acceder a ellos. Asimismo esta zona de memoria es fundamental a la hora de trabajar con las interrupciones, ya que dado que la interrupción puede llegar en cualquier momento, necesitamos un área de memoria donde guardar el estado del procesador, incluyendo la información del banco en el que se esté trabajando inmediatamente antes de ejecutar la interrupción y luego volver a el mismo estado antes de retomar la ejecución normal del programa.

9

Page 10: Microcontroladores PIC

Este es un ejemplo de cómo se especifica el significado de los bits de un SFR (STATUS en este caso) en el datasheet del PIC.El indicativo ‘R/W-0’ que aparece encima de algunos bits, significa que el bit es legible (R) y escribible (W) y que su valor después de un reset de la alimentación es 0. Observe otros casos como R/W-x o R-1 que se explican en la leyenda.De este registro son especialmente importante los bits de acarreo (C carry) y cero (Z) que nos indican si la última operación ejecutada produjo rebosamiento (overflow) o dio como resultado 0 respectivamente. Los bits RP0 y RP1 son los que seleccionan los bancos de memoria RAM.

Entre los dispositivos contenidos en los PIC posiblemente los puertos son los más usados. Cada puerto tiene como mínimo dos SFR’s asociados: uno para las lecturas del propio puerto PORTx y otro denominado TRISx que determina para cada bit si se usará como entrada o salida digital. El nemónico TRISx proviene de TRi-State logic ya que cada patilla de los puertos tiene un driver tri-state, o sea que además de los estados alto ‘1’ o bajo ‘0’, tiene un tercer estado conocido como de alta impedancia, lo que significa que la línea del puerto queda libre o flotante. Por tanto si activamos un bit de la palabra TRISA, estaremos activando el estado de alta impedancia en el driver de esa línea y por tanto podremos leer el estado que ponga en ella la circuitería exterior, o sea, esa línea queda configurada como una línea de entrada. Inversamente si ponemos un 0 en un bit de la palabra TRISA, estamos activando su línea correspondiente como línea de salida. Podemos escribir en el SFR TRISx en cualquier momento de la ejecución del programa, pero lógicamente si por error se activa como salida una línea conectada a una salida de la circuitería externa se puede producir un corto-circuito de los dos drivers (el del PIC y el de la circuitería externa) que podría ocasionar averías; es importante por lo tanto que el programador defina correctamente las líneas de entrada y salida de todo el dispositivo.

10

Page 11: Microcontroladores PIC

Lógicamente antes de trabajar con los PIC’s hay que instalar el software (gratuito) MPLAB y PICkit2 que son el programa ensamblador y el programador/debugger de MicroChip que usaremos. En la web de www.MicroChip.com existe una cantidad ingente de información, sobre estos programas, datasheets, application notes, etc. Una vez instalados conectamos el PICkit2 con un cable USB, si todo está bien el dispositivo será reconocido y queda listo para trabajar. MPLAB es muy potente y entre sus muchas cualidades está la de tener un simulador, que en caso de no disponer de un PIC real, se puede usar para aprender o hacer desarrollos y pruebas.

En MPLAB pulsamos File->Open ‘Hello World.asm’ vemos que se abre una ventana con el fichero de texto que usa distintos colores para resaltar la sintaxis; luego Programmer->SelectProgrammer->PICkit2 y ejecutamos Project->Quick Build Hello World.asm (deberá estar seleccionada la ventana con este programa fuente para que esta opción esté activada). Debemos chequear también que el sistema ha reconocido el PIC que tenemos conectado, clickar en Configure->Select Device y comprobar que aparece el PIC16F690 en nuestro caso. Si queremos que aparezcan los números de línea en la ventana del texto del programa, hacer click con el botón derecho sobre la ventana y seleccionar ‘Properties’, luego en la pestaña de ‘ASM File Types’ marcar ‘Line Numbers’ o cualesquiera otra opción a nuestro gusto. Después una vez que la compilación ha terminado correctamente, podremos escribir el programa en el PIC, para esto ejecutar Programmer->Program en MPLAB, o en PICkit2 Programmer seleccionar File->Import HEX, y pulsar el botón Write.

En la ventana PICkit2 Programmer podemos pulsar el botón Read y vemos que el dispositivo tiene las primeras posiciones de la memoria de programa (FLASH) escritas, todos los bits que no están escritos aparecen como 1, y dado que el PIC tiene 14 bits por palabra, en hexadecimal vemos 3FFF en todas las demás posiciones de memoria. Si pulsamos ‘On’ en la zona de VDD PICkit 2, aplicaremos la tensión que aparece a la derecha (5.0 V en nuestro caso) a la tarjeta de desarrollo, e inmediatamente veremos que el programa grabado se ejecuta. En este caso muy simple el programa tiene sólo 5 instrucciones tal y como puede verse en la ventana del PICkit2 programmer, y al ejecutarse enciende el LED conectado a la patilla 0 del puerto C.

11

Page 12: Microcontroladores PIC

Vemos en la diapositiva el dispositivo PICkit2 de MicroChip conectado a placa de desarrollo ‘Low Pin Count’ que ha sido modificada por el autor para probar las distintas funcionalidades del microcontrolador. La tarjeta de desarrollo original contiene el PIC, los 4 LEDs rojos, el potenciómetro y el pulsador; se han añadido los transistores T1 y T2 junto con los displays de 7 segmentos; el sensor de humedad; el display LCD y el MOSFET T3 para controlar el pequeño motor de corriente continua. Disponemos también de otras tarjetas de demostración con un PIC16F887 SMD de 44 pines. Ambos tipos de tarjetas son muy convenientes para ser usadas en aplicaciones pequeñas que no requieran mucha circuitería extra, o como un módulo que se puede integrar en un sistema mayor a través de los conectores adecuados para aplicaciones más grandes.

A lo largo de esta introducción veremos programas para medir y/o controlar todos y cada uno de estos dispositivos. A veces usaremos también alguna placa de desarrollo de MikroElektronika, llamadas EasyPIC que son mucho más grandes y tienen por tanto mayor número de dispositivos y posibilidades. Para la mayoría de los programas en C usaremos los EasyPIC, aunque también algunas aplicaciones para la placa de desarrollo modificada para ilustrar el uso del programador PICkit2 con programas desarrollados con mikroC.

La mayoría de los programas en ensamblador que veremos a continuación son los desarrollados por MicroChip para ilustrar la forma de programar los PICs con MPLAB, y como se pueden cargar los programas en la propia placa de aplicación sin necesidad de sacar el PIC del circuito, esto es lo que se conoce como ICSP (In-CircuitSerial Programming); que ya habíamos mencionado como una de las grandes ventajas de los PIC.

12

Page 13: Microcontroladores PIC

Este pequeño programa está escrito en lenguaje ensamblador de los PICs, cabe destacar:

•Todo lo que esté detrás de un punto y coma se considera comentario.•Las líneas que empiezan con # son directivas para el preprocesador, también en general las que empiezan por ‘__’ y algunas palabras especiales como org o end que son palabras reservadas. Ninguna directiva produce código ejecutable, pero pueden modificar la forma en que el compilador lo produce.•La directiva ‘#include’ carga una serie de definiciones específicas para el PIC con el que estamos trabajando, contenidas en el fichero p16F690.inc en un cierto directorio de MPLAB. Esto nos permite usar en el programa nombres para las posiciones de memoria y los SFR (Special Function Registers) que hacen el programa más legible y fácil de entender. Si no pusiéramos esta directiva el compilador se quejaría, ya que no sabría que significa STATUS, PORTC, RP0, etc.•La directiva __CONFIG se usa para especificar los bits de configuración del PIC, éstos bits se escriben en el momento de la escritura de la memoria de programa y configuran dispositivos como el oscilador, el watchdog, los retardos de arranque, el detector de voltaje bajo (brown-out), etc. Nota: no confundir la palabra de configuración (CONFIG ver datasheet pg. 174) con un SFR, ya que esta palabra sólo se escribe en el momento del burning del dispositivo y no puede cambiarse durante la ejecución.•La directiva ‘org x’ establece que el código que sigue debe ensamblarse a partir de la dirección ‘x’. En este caso 0 es la dirección por la que siempre empiezan los PIC’s a ejecutar después de un reset.•La palabra ‘Start’ sin indentar es una etiqueta, o sea es un identificativo para esa posición del programa a la que podremos dirigirnos con la orden ‘goto Start’. La sintaxis más usada añade dos puntos ‘:’ al final de las etiquetas pero MPLAB puede trabajar de las dos formas.•El comando ‘bsf f,b’ significa ‘Bit Set File la_celda_f, el_bit_b’; al ejecutarse pone a 1 (set) el bit b de la celda f, en nuestro caso es el bit 5 (RP0) de la celda 3 (STATUS), y por tanto a partir de ese momento accederemos al banco 1 de la memoria RAM. (Pregunta: ¿ por qué sabemos que el bit RP1 es 0 ?).•El comando ‘bcf f,b’ significa ‘Bit Clear File la_celda_f, el_bit_b’; al ejecutarse pone a 0 (clear) el bit b de la celda f, en nuestro caso es el bit 0 de la celda 87h (TRISC), y por tanto la línea 0 del puerto C se activa como salida.•Luego seleccionamos el banco 0: bcf STATUS,RP0.•Finalmente activamos el bit 0 del puerto C: bsf PORTC,0. Pone un 1 en el bit bajo de la celda 07 (PORTC). Dado que este puerto tiene un LED enchufado (a través de una resistencia) el LED se encenderá.•El comando ‘goto $’ salta sobre sí mismo de forma que ya no se ejecuta nada más. El goto es un salto a la dirección especificada, y ‘$’ significa la posición actual, también puede usarse con incrementos, por ejemplo ‘goto $-1’ salta al comando anterior.•‘End’ es una directiva que le indica al compilador que no siga compilando, o sea fin del programa.

13

Page 14: Microcontroladores PIC

Ya hemos visto las instrucciones BSF y BCF en el ejemplo anterior.

Pregunta: ¿ sobre qué banco actúa la instrucción: BCF 0x71,2?R: En principio en general el banco depende de las instrucciones de selección de banco que se hayan ejecutado antes, y por tanto en esa parte de programa que se ha dado como ejemplo no aparece ninguna operación de selección de banco, luego parece que no podríamos determinarlo con la información que tenemos. Sin embargo la dirección 0x71 está en la zona de memoria mapeada en todos los bancos, o lo que es lo mismo independiente del banco; luego la respuesta correcta es en todos los bancos.

Las instrucciones BTFSC y BTFSS (Bit Test File Skip if Clear o Set) nos permiten ejecutar o no la siguiente instrucción en función del valor del bit que se testea. Notar que la lógica de estas instrucciones es un poco confusa ya que la siguiente instrucción no se ejecuta cuando la condición es verdadera, sino falsa; ya que la instrucción está formulada negativamente (skip = no ejecutar), podríamos decir: ‘test bit, no ejecutar si clear’.

14

Page 15: Microcontroladores PIC

Casi todas estas instrucciones tienen un sufijo ‘,d’ que es opcional y determina el destino del resultado de la operación; ‘d’ puede valer 1 ó 0 según el destino sea la celda de memoria o el acumulador (W) respectivamente. Realmente esto es un ‘truco’ para mantener bajo el número de instrucciones (arquitectura RISC) pero a la vez cada una de estas instrucciones puede ejecutarse de una forma o de otra. Por defecto d=1, o sea que si no se especifica ‘d’ el destino es la posición de memoria. En los ‘include’ de MPLAB están definidas f=1 y w=0, para hacer el código más fácil de entender; esto nos permite por ejemplo usar:

incf Contador,f ; Contador = Contador + 1; equivalente a ‘incfContador’

incf Contador,w ; W = Contador + 1, la variable Contador no cambia

El funcionamiento de cada instrucción está dado en la columna ‘Function’ y no hay mucho más que añadir; cabe quizás mencionar que la operación NOP se puede usar para introducir un pequeño retardo, pero hay que considerar que este retardo depende de la velocidad de reloj lógicamente. Como todas las instrucciones simples NOP se ejecuta en 4 ciclos de reloj.También son interesantes los comandos DECFSZ y INCFSZ que se usan para hacer bucles; por ejemplo para ejecutar 100 veces determinado grupo de instrucciones podríamos hacer:

movlw .100 ; NOTA: esta es la manera por defecto de introducir números en decimal

movwf ContadorLoop100:

< grupo de instrucciones a ejecutar ….>

decfsz Contadorgoto Loop100 ; si el Contador es != 0

vuelve a ejecutar el Loop

Los PIC son atípicos en el sentido de que muchas de éstas operaciones hay que aplicarlas forzosamente sobre la memoria, y no existe la posibilidad de aplicarlas directamente al acumulador (W), como por ejemplo: COMF, DECF, INCF, RLF, RRF o SWAPF.

15

Page 16: Microcontroladores PIC

Estas instrucciones nos permiten introducir datos fijos (literal) en contraposición a los datos que podemos almacenar en la memoria, que pueden cambiar durante la ejecución de un programa.

Los PIC de gama media tiene una pila de subrutinas de 8 niveles, y de 31 niveles para los de gama alta. Una subrutina es una porción de código a la que se puede llamar desde varias posiciones en el programa, y una vez acabada la ejecución de la subrutina, la ejecución vuelve a la instrucción siguiente a la de la llamada a la rutina. A su vez dentro de una rutina, se puede llamar a otra subrutina y así sucesivamente hasta un máximo de 8 niveles (ó 31 para la gama alta). Normalmente hay que tener en cuenta que si se usan interrupciones tendremos que reservar tantos niveles como se usen en el programa más los de la rutina de interrupciones más uno, que es el nivel que usa la propia interrupción. La llamada a una rutina se hace con CALL <etiqueta>, y el retorno desde las rutinas se hace al ejecutar RETURN o ‘RETLW xx’; desde la rutina de servicio de la interrupción hay que usar RETFIE.

Nota: TOS significa ‘Top Of Stack’, es un puntero a la pila donde se almacenan las posiciones de retorno de las rutinas. PC significa ‘Program Counter’ y es la dirección de la instrucción de programa que se está ejecutando.

El comando CLRWDT es el que resetea el contador del watchdog (WDT), evitando la re-inicialización del procesador si el watchdog está activado y se ha consumido el tiempo de gracia.Los comandos OPTION y TRIS se mantienen por compatibilidad con PIC’s antiguos, no son ya necesarios y las próximas versiones no los incorporarán.

16

Page 17: Microcontroladores PIC

Notes1: When an I/O register is modified as a function of itself (e.g., MOVF PORTA, 1), the value used will be that value present on the pins themselves. For example, if the data latch is ‘1’ for a pin configured as input and is driven low by an external device, the data will be written back with a ‘0’.2: If this instruction is executed on the TMR0 register (and where applicable, d = 1), the prescaler will be cleared if assigned to the Timer0 module.3: If the Program Counter (PC) is modified, or a conditional test is true, the instruction requires two cycles. The second cycle is executed as a NOP.

Esta es la tabla completa de las 35 instrucciones de los PIC de gama media.Los tiempos de ejecución son casi siempre 1 ciclo, equivalente a 4 periodos del reloj; para las operaciones que requieren un salto son dos ciclos.Es importante observar los bits del registro de estado (STATUS) que se ven afectados por las operaciones; las operaciones aritméticas (ADD, SUB) afectan a C, Z y DC; las operaciones lógicas sólo a Z, las de rotación sólo al C y cabe destacar que las operaciones INCFSZ, DECFSZ, MOVxx o SWAPF no afectan a ningún bit de STATUS, excepto MOVF que afecta a Z. Observe en la columna del ‘opcode’ o código de la instrucción que la dirección de memoria (o file) tiene 7 bits, esto es lo que determina que el tamaño de los bancos de memoria sea de 128 bytes ya que esto es lo máximo que podemos direccionar con los 7 bits de la instrucción. Análogamente las direcciones de salto de GOTO y CALL tienen 11 bits, lo que nos permite un rango de salto de 2048 (lo que se conoce como una página), para saltos a más distancia hay que hacer uso del SFR PCLATH, que nos permite acceder a todas las páginas que tenga la memoria de programa del dispositivo.

Al principio es útil tener esta tabla a mano para entender los programas y al escribir nuestros primeros programas en ensamblador.

17

Page 18: Microcontroladores PIC

Además de las 35 instrucciones mencionadas anteriormente, MicroChip ha introducido esta serie de instrucciones especiales (en realidad macros) para facilitar la programación. Las ‘macros’ son instrucciones del pre-procesador (no confundir con las instrucciones ejecutables simples) que especifican una equivalencia del nombre de la macro con el cuerpo de la definición de dicha macro y están soportadas por MPLAB; así por ejemplo podríamos definir en un programa:

#define SALTA GOTO

esta sentencia define la macro ‘SALTA’ como ‘GOTO’; las macros son sustituidas antes de la compilación por su equivalente, luego en este caso si tenemos una sentencia en el programa ‘SALTA Posicion3’, es equivalente a ‘GOTO Posicion3’.

MicroChip introduce estas macros en los ficheros ‘#include p16F690.inc’ que acompañan al MPLAB y que además contienen los nombres de los SFRs de cada tipo de microcontrolador como ya habíamos mencionado. Todas estas instrucciones se obtienen en realidad como una combinación de las instrucciones simples que realmente son las que ejecuta el microcontrolador, por este motivo estas instrucciones rompen la regla de que ‘todas’ las instrucciones de los PIC se ejecutan en 4 u 8 ciclos de reloj, ya que por ejemplo BNZ puede tardar 12 ciclos; otras sin embargo no son más que nombres más adecuados a los ‘poco afortunados’ BTFSC o BTFSS.

En los PIC de gama alta la mayoría de estas instrucciones son instrucciones reales, y además se han introducido otras muchas hasta un total de 83 por lo que ya no pueden considerarse estrictamente RISC, pero este juego de instrucciones extendido está indicado y fue diseñado para permitir la programación de estos dispositivos en lenguajes de alto nivel, particularmente el C. Algunas de las instrucciones extendidas hay que activarlas en la configuración del dispositivo.

18

Page 19: Microcontroladores PIC

Entre las líneas 31 y 34 tenemos la declaración de dos variables Delay1 y Delay2 usando la directiva ‘cblock’, que le indica al compilador que necesitamos un bloque de datos a partir de la posición de memoria 0x20. En realidad en este caso lo único que estamos haciendo es asignar a los símbolos Delay1 y Delay2 los valores 0x20 y 0x21 respectivamente, pero si tenemos un bloque de memoria con más elementos es más cómodo que el compilador se encargue de asignarle sus posiciones consecutivas.

El programa es simple: primero inicializa el puerto C, enciende el LED, espera un cierto tiempo en un doble bucle y apaga el LED, vuelve a esperar la misma cantidad de tiempo y salta a la sentencia que enciende el LED para repetir todo el proceso desde ese punto.

Preguntas:

•¿ Por qué no se inicializan las variables Delay1, Delay2 ? ¿ es esto correcto ? R: no pero sólo afecta al primer bucle, por lo que es casi irrelevante.•Hay dos partes en el código que son idénticas y por tanto este programa se puede estructurar mejor creando una subrutina. Se propone como ejercicio.•También se puede re-diseñar la rutina del ejercicio anterior para que acepte una variable que determine el retardo total. De esta forma se hace mucho más fácil hacer más rápido o lento la velocidad de parpadeo; se puede también cambiar la relación encendido/apagado o incluso modificarlo durante la ejecución del programa para que poco a poco vaya acelerando y vuelta a empezar.

19

Page 20: Microcontroladores PIC

Esencialmente el programa (Rotate.asm) es muy similar al anterior. Ahora definimos todo el puerto C como de salida y usamos la variable Display para almacenar el valor de los bits que pasamos a los cuatro diodos LED y para realizar la rotación, tal y como puede verse en las líneas 60 a 64.

¿ Se podría usar PORTC como variable en vez de Display ? La respuesta es sí, y en la mayoría de los casos funcionaría bien; pero podría darse un efecto colateral indeseado, ya que si por ejemplo la línea 3 del puerto está cortocircuitada la lectura encubierta que se hace durante la rotación (rrf PORTC,f) nos devolvería 0 al intentar activar la línea 3 y por tanto la rotación se pararía en dicho punto.

Ejercicio: Hay un error intencionado en el programa, relacionado con un comentario. ¿ Cual es ?Respuesta en la siguiente diapositiva.

20

Page 21: Microcontroladores PIC

Vemos aquí el diagrama hardware de dos líneas más o menos típicas de los puertos que podemos encontrar en los PIC. La complejidad que podemos observar se debe a las múltiples conexiones posibles de algunos pines; en el caso de la derecha se trata del pin RC7 (PORTC.7) y como vemos está conectado al conversor A/D y hay varios drivers y dos flip-flops para el control de la línea. El flip-flop de PORTC es el que proporciona el dato de salida, mientras el flip-flop TRISC controla la habilitación del driver de salida. Vemos también que la lectura digital de este pin sólo puede hacerse si no está habilitada su línea de selección de modo analógico, ya que si el modo analógico está seleccionado para esta línea siempre leeremos 0 lógico. También observamos que para esta línea se puede seleccionar la función SDO, en cuyo caso el nivel lógico a la salida es independiente del valor escrito en el flip-flop del PORTC.

En el diagrama de la izquierda, que corresponde a la patilla RA2 (PORTA.2), vemos que además de los flip-flops para PORTA y TRISA, tenemos WPUA e IOCA; éstos SFRs controlan la funcionalidad de weak-pull-up y la de interrupt-on-change que posee este pin; análogamente existen WPUB e IOCB con las mismas funcionalidades para el puerto B. También vemos que tiene funciones relativas al TMR0 y tiene la capacidad de generar interrupciones desde dispositivos externos (INT).

Una característica que afecta a todas las líneas de los puertos de los PIC es que *siempre* usan una secuencia de lectura-modificación-escritura (Read-Modify-Write o RMW), lo que significa que cualquier instrucción que especifique un registro ejecuta un ciclo RMW, esto es, el registro es leído, los datos modificados, y el resultado es almacenado de acuerdo con la instrucción. En otras palabras: una operación de lectura se lleva a cabo en el registro incluso si la instrucción aparentemente “sólo” escribe en dicho registro. Esto puede tener resultados indeseados en la interacción con el hardware, que el programador siempre debe de tener en cuenta. En los PIC de gama alta existe un tercer registro (LATA, LATB, etc) que puede leer el dato del latch no el del puerto de salida.

Respuesta al ejercicio de la diapositiva anterior: ‘movlw 13’ no da 1 centésima de segundo, ya que 13h es 19d y por tanto el tiempo es un 50% mayor.

21

Page 22: Microcontroladores PIC

Vemos que tiene 14 entradas posibles, que se selecciona con los 4 bits CHS<3:0> de la palabra ADCON0. La entrada seleccionada se digitaliza con 10 bits (0 .. 1024 niveles) comparada con la tensión de referencia. Esta tensión de referencia se puede seleccionar con el bit VCFG, bien la tensión de alimentación o una tensión menor aplicada a la patilla RA1. El SFR que controla casi todo esto es:

ADCON0 – A/D CONTROL REGISTER (ADDRESS: 1Fh)

--------------------------------------------------------------------------------------

| R/W-0 | R/W-0 | R/W-0 | R/W-0 | R/W-0 | R/W-0 | R/W-0 | R/W-0 |--------------------------------------------------------------------------------------

| ADFM | VCFG | CHS3 | CHS2 | CHS1 | CHS0 |GO/DONE|ADON |--------------------------------------------------------------------------------------

bit 7

bit 0

bit 7 ADFM: A/D Result Formed Select bit

1 = Right justified (ADRESH contains the two most significant bits)

0 = Left justified (ADRESH contains the eight most significant bits)

bit 6 VCFG: Voltage Reference bit

1 = VREF pin

0 = VDD

bit 5-2 CHS<3:0>: Analog Channel Select bits

0000 = Channel 00 (AN0); 0001 = Channel 01 (AN1); ………… 1011 = Channel 11 (AN11)

1100 = CVREF

1101 = VP6 Esta es una referencia de voltaje interna de 0.60 V1110 = Reserved. Do not use.

1111 = Reserved. Do not use.

bit 1 GO/DONE: A/D Conversion Status bit

1 = A/D conversion cycle in progress. Setting this bit starts an A/D conversion

cycle.

This bit is automatically cleared by hardware when the A/D conversion has

completed.

0 = A/D conversion completed/not in progress

bit 0 ADON: A/D Enable bit

1 = A/D converter module is enabled

0 = A/D converter is shut off and consumes no operating current

22

Page 23: Microcontroladores PIC

La selección de canal es muy sencilla, hay un bit para cada canal en los SFR ANSEL y ANSELH.Notar que para el PIC16F690 por defecto todos los canales están en modo analógico después de un reset.

IMPORTANTE: hay grandes diferencias en la forma de seleccionar los canales analógicos de un modelo de PIC a otro y es *imprescindible* consultar la documentación para el correcto control de las entradas.

Un aspecto importante del control del convertidor A/D es la velocidad de conversión, que se establece con el SFR ADCON1; este SFR tiene sólo 3 bits del 6 al 4 con el siguiente significado:

ADCON1 – A/D CONTROL REGISTER 1 (ADDRESS: 9Fh)— ADCS2 ADCS1 ADCS0 — — — —bit 7 bit 0

bit 7 Unimplemented: Read as ‘0’bit 6-4 ADCS<2:0>: A/D Conversion Clock Select bits

000 = FOSC/2001 = FOSC/8010 = FOSC/32x11 = FRC (clock derived from a dedicated internal oscillator = 500 kHz max)100 = FOSC/4101 = FOSC/16110 = FOSC/64

bit 3-0 Unimplemented: Read as ‘0’

23

Page 24: Microcontroladores PIC

These steps should be followed for an A/D conversion:

1. Configure the A/D module:• Configure analog/digital I/O (ANSx)• Select A/D conversion clock (ADCON1<6:4>)• Configure voltage reference (ADCON0<6>)• Select A/D input channel (ADCON0<5:2>)• Select result format (ADCON0<7>)• Turn on A/D module (ADCON0<0>)

2. Configure A/D interrupt (if desired):• Clear ADIF bit (PIR1<6>)• Set ADIE bit (PIE1<6>)• Set PEIE and GIE bits (INTCON<7:6>)

3. Wait the required acquisition time.4. Start conversion:

• Set GO/DONE bit (ADCON0<1>)5. Wait for A/D conversion to complete, by either:

• Polling for the GO/DONE bit to be cleared(with interrupts disabled); OR• Waiting for the A/D interrupt

6. Read A/D Result register pair(ADRESH:ADRESL), clear bit ADIF if required.

7. For next conversion, go to step 1 or step 2 asrequired. The A/D conversion time per bit isdefined as TAD. A minimum wait of 2 TAD isrequired before the next acquisition starts.

24

Page 25: Microcontroladores PIC

Este programa configura el conversor A/D para leer la entrada PORTA(0) = AN0 = RA0 comparada con la tensión de alimentación (referencia Vdd); el reloj de digitalización es de Fosc/16.Nótese el uso de NOP’s para introducir un pequeño retardo desde la activación del A/D hasta el inicio de la conversión. Este retardo se necesita para que se estabilice el voltaje a la entrada del módulo A/D.

Aquí introducimos una manera más cómoda de selección de bancos de memoria: en vez de poner explícitamente los bits RP0 y RP1 de la palabra STATUS, usamos la directiva ‘banksel<nombre_registro>’, el pre-compilador introducirá los comandos BSF o BCF necesarios para seleccionar el banco en el que se encuentra el registro nombrado.

Advertencia: el uso de BANKSEL siempre pone todos los bits de selección de banco al valor necesario, esto puede ser ineficiente ya que la mayoría de las veces no es necesario cambiarlos todos. Por otra parte el compilador siempre genera un warning cuando accedemos a cualquier posición de memoria fuera del banco 0, tal y como puede verse en la figura referido a la línea 63 del programa donde se accede a TRISA que está en el banco 1, y aunque tengamos la directiva BANKSEL en la línea anterior … Esto es claramente mejorable por parte de los desarrolladores de MPLAB, si bien es cierto que no siempre se puede asegurar que en alguna otra parte del programa haya un salto a la línea 63 y podría darse el caso de que se accediera a otro banco. Es responsabilidad del programador asegurarse de que este problema no se presenta.

Al ejecutar este programa sobre la tarjeta de desarrollo podemos cambiar el valor del voltaje en RA0 usando el potenciómetro en la placa y veremos como los 4 LEDs presentan los bits más significativos de la conversión, siendo equivalente a la presentación en binario del valor del voltaje/Vdd * 16, ya que se trata de los 4 bits más significativos. Es interesante mover el potenciómetro cerca de la transición entre 7 y 8 por ejemplo y veremos como el ruido afecta a las conversiones, ya que observaremos cambios en la lectura debido normalmente a interferencias.

25

Page 26: Microcontroladores PIC

Este programa (rebotes.asm) demuestra que si no se toman precauciones, las operaciones manuales de pulsación de teclas producen rebotes, es decir el interruptor se activa y desactiva más veces de las que deseamos.

A la entrada del puerto A<3> tenemos una resistencia de pull-up y un pulsador a tierra. Notar cómo a la hora de configurar la patilla 3 del puerto A como entrada digital, no basta con hacer el TRISA<3> igual a 1, sino que en general también se requiere deshabilitar la entrada analógica correspondiente en el registro ANSEL.

Preguntas: ¿ Son correctos los comentarios del programa ? ¿ Cuándo se actualiza la cuenta, al pulsar o al soltar la tecla ? R: Los comentarios de las líneas 31 y 36 están intercambiados, por lo se la cuenta se incrementa al soltar la tecla.

Ejercicio: modificar el programa para que cuente cambios de estado de la tecla, o sea en cada pulsación debería contar 2 veces, uno al pulsar y otro al soltar la tecla.

26

Page 27: Microcontroladores PIC

Esta versión del programa llamada Debounce.asm, espera a detectar la tecla estable 5 veces, con un retardo de 1 ms entre cada testeo.

La eliminación de los rebotes también se puede hacer por métodos ‘hardware’ tal y como puede verse en las dos figuras de la diapositiva; en la primera por filtrado (paso bajo) y en la segunda usando un flip-flop tipo RS. Obviamente usando la potencia de microcontrolador nos ahorramos la colocación de esta circuitería externa y simplificamos nuestro diseño.

Observar como se puede comprobar si una variable (Counter en este caso) alcanza un determinado valor en las líneas 73 y siguientes.

27

Page 28: Microcontroladores PIC

Entre los programas fuente que acompañan la presentación encontramos 3 que son muy similares: Vsrotate.asm, Reversible.asm, y Function.asm. Los dos primeros no añaden nada nuevo por lo que se deja al lector para su compilación y estudio; en ambos casos se lee el A/D y se establece una rotación de los LEDs con un retardo que es función del valor leído del A/D. Así moviendo el potenciómetro los LEDs se desplazan a mayor o menor velocidad. En el programa Reversible.asm también se puede cambiar el sentido de desplazamiento del LED encendido al pulsar el interruptor de la tarjeta. Finalmente Function.asm realiza la misma función pero introduce una subrutina para manejar el tiempo de retardo, como podemos ver en la diapositiva desde la línea 116 hasta las 123.

Una llamada a la rutina aparece en la línea 85; vemos que el acumulador (W) se usa para pasar un ‘parámetro’ a la rutina, en este caso es el número de bucles de segundo nivel que realizaremos; cuanto mayor sea el valor de W mayor será el retardo generado.

También es posible que una rutina ‘devuelva’ algún valor; en ese caso puede usarse W o si son más de un valor pueden usarse variables creadas al efecto.

28

Page 29: Microcontroladores PIC

Entre otras funciones el SFR OPTION_REG controla al temporizador Timer0. La señal de reloj que alimenta al Timer0 puede derivarse por división del reloj principal o puede introducirse al PIC por una línea de entrada. Con el bit T0CS podemos seleccionar como fuente para el Timer0 la patilla 2 del puerto A, y de esta forma el temporizador estaría funcionando como un contador; en cuyo caso el bit T0SE permite seleccionar si la cuenta se hace en el flanco de bajada o el de subida.

El Timer0 comparte su pre-scaler con el watchdog (WDT), en función del valor del bit PSA se usará para uno o el otro pero nunca simultáneamente para los dos.Finalmente los tres bits bajos PS<2:0> seleccionan el valor de división del pre-scaler,note que los valores de división son distintos para el TIMER-0 y el WDT.

El Timer0 tiene 8 bits, y por tanto cuenta desde 0 a 255, al siguiente pulso vuelve a 0 y se genera una interrupción (ver diapositiva siguiente).

29

Page 30: Microcontroladores PIC

Una interrupción es una señal que recibe el controlador para atender un suceso detectado por el hardware periférico. En los PIC existen numerosos periféricos que pueden generar interrupciones: como los timers, el cambio de estado en una línea de un puerto, el conversor analógico/digital, los comparadores, el módulo de comunicaciones serie, el módulo de comparación y captura, la EEPROM, etc. Asimismo un sistema externo al microcontrolador puede también generar una interrupción a través de un determinado flanco en la línea de interrupción externa (RA2/INT en el PIC16F690).

Para que una interrupción afecte al microcontrolador, deberán están habilitadas las interrupciones en general (INTCON<GIE> ver diapositiva siguiente); para los periféricos del microcontrolador deberá también estar habilitadas las interrupciones de periféricos (PEIE), así como las interrupciones del periférico en particular que la genera (ADIE, RCIE, TXIE, TMR1IE, etc.). Para los sistemas externos, el timer 0 ó los puertos, deberá estar habilitada INTE, T0IE o RABIE respectivamente. Si no se dan estas condiciones de habilitación, la interrupción no afecta a la ejecución. Si por el contrario se recibe una interrupción que está habilitada el controlador termina de ejecutar la instrucción actual (en algunos casos puede haber más retardo – latencia), pone GIE a 0 para deshabilitar cualquier otra interrupción, y ejecuta un salto a la dirección 4 donde se debe encontrar la rutina de servicio de las interrupciones. Una vez acabada la rutina de servicio de interrupciones se ejecuta el retorno a la instrucción siguiente a la que se terminó de ejecutar cuando se recibió la interrupción, usando para ello la instrucción RETFIE que vuelve ahabilitar GIE. Dentro de la rutina de servicio de interrupciones se debe salvar el contexto de ejecución del programa principal (WREG y STATUS/banco de trabajo), y este contexto deberá recuperarse justo antes de volver de la interrupción, para que la ejecución normal pueda continuar tal y como se había quedado. Para esto es necesario reservar 2 celdas de memoria en la zona común de todos los bancos (Ejercicio: razone por qué debe ser en la zona común y no en el banco 1 por ejemplo.) que nos permitan almacenar el working register W y la palabra STATUS. Este código nos permite realizar estas operaciones:

cblock 0x70W_TEMPSTATUS_TEMP

endc

MOVWF W_TEMP ;Copy W to TEMP registerSWAPF STATUS,W ;Swap status to be saved into W, SWAPF doesn’t change any flagCLRF STATUS ;bank 0, regardless of current bank, Clears IRP,RP1,RP0MOVWF STATUS_TEMP ;Save status to bank zero STATUS_TEMP register::(ISR) ;Insert user code here:SWAPF STATUS_TEMP,W ;Swap STATUS_TEMP register into WMOVWF STATUS ;Move W into Status register (sets bank to original state)SWAPF W_TEMP,F ;Swap W_TEMPSWAPF W_TEMP,W ;Swap W_TEMP into WRETFIE ; TOS -> PC ; 1 -> GIE

Pregunta: parece más sencillo ejecutar ‘movf W_TEMP,w’ al final en vez de dos SWAPF … ¿Por qué no se usa ‘movf’ ? R: Porque movf altera el valor del flag Z en STATUS.Una vez dentro de la rutina de servicio de interrupciones podemos saber que dispositivo ha generado la interrupción mirando los flags de INTCON, PIR1 y PIR2; sólo un flag puede estar a 1, y este flag debe ser puesto a 0 por el software de la ISR.

30

Page 31: Microcontroladores PIC

INTCON es el registro (SFR) de control de las interrupciones. Como hemos visto una interrupción es una señal generada por algún dispositivo interno o externo al PIC y que requiere una acción inmediata; el registro INTCON nos permite habilitar o no las interrupciones que pueda generar cada dispositivo.

Relativos al Timer0 tenemos dos bits el T0IE que permite habilitar la interrupción del Timer0; y T0IF es un flag que nos indica que el Timer0 ha llegado al final de su cuenta. Este flag deberá ser limpiado por el software antes de que ocurra el siguiente overflow. Este flag se actualizará independientemente de que las interrupciones estén habilitadas.

Además de este registro INTCON, existen los registros PIE1 y PIE2 que son los ‘Peripheral Interrupt Enable registers’ ; en ellos se puede habilitar la interrupción de los dispositivos internos (periféricos) como el A/D, USART, Timer1, Timer2, etc.

En los PIC de gama alta se pueden definir la prioridad de las interrupciones como alta o baja; lógicamente existen dos vectores de interrupción en las posiciones 0x08 y 0x18 respectivamente, y muchas otras posibilidades de configuración.

31

Page 32: Microcontroladores PIC

Vemos en este sencillo programa que el uso del temporizador lógicamente puede simplificar la generación de retardos o en general el control del tiempo. Insertado en el programa se ha incluido un diagrama simplificado del Timer0.

En la línea 36 seleccionamos un factor de división de 256 de la frecuencia de entrada; en este caso la señal seleccionada es el ciclo de instrucción del microcontrolador, que a su vez es Tosc*4 ya que cada instrucción requiere 4 ciclos de reloj para su ejecución. En total pues el periodo de la señal que alimenta al temporizador es 1024*Tosc. En nuestro caso el oscilador es de 8 MHz por lo que el periodo del Timer0 es:

Timer0 = 1024 * 1/8 us = 128 us

el flag de overflow se activará cada 256 impulsos recibidos, dando un total de: 32.768 ms.En la línea 42 el programa espera sin hacer nada ‘útil’; normalmente el procesador podría dedicarse a cualquier otra cosa mientras el temporizador lleva la cuenta del tiempo transcurrido. Nótese que el flag T0IF se debe poner a cero en el software. En este programa usamos el flag de fin de cuenta del TIMER-0 pero no tenemos habilitadas las interrupciones, y por tanto no hemos definido la rutina de servicio de las mismas; en la diapositiva siguiente vemos un ejemplo con interrupciones.

Pregunta: de nuevo hay un pequeño error en el programa, ¿ cual es ? ¿ qué efecto tiene ? R: En la línea 38 no accedemos al banco correcto y por tanto en el primer bucle Display no está inicializado correctamente.

32

Page 33: Microcontroladores PIC

Programa de ejemplo de uso de interrupciones ‘interrupt.asm’; este programa usa las interrupciones que se generan con el overflow del Timer0. En la rutina de servicio de las interrupciones carga en el valor de conversión en el registro del TMR0 por lo que acelera o decelera la rotación del encendido de los LEDs en la placa de desarrollo.

Nótese en la línea 52 que la primera instrucción es ahora un salto al principio del programa normal, ya que la rutina de servicio de las interrupciones (ISR) empieza forzosamente en la posición 4. Observe entre las líneas 61 y 66 como se puede averiguar que periférico ha generado la interrupción mirando los flags en INTCON y PIR1, y como se limpia por software el flag en la línea 70. ‘T0Semaphore’ es una posición de memoria que se usa para transmitir información entre la ISR y el programa principal; es un flag que indica que el temporizador ha terminado la cuenta.

En el trozo de programa visualizado en la diapositiva (que es el programa original de MicroChip) hay un error tipográfico en las sentencias de chequeo de los flags, ¿ puede señalarlo ?Asimismo hay otro error más grave que hace que no se espere correctamente el final de la conversión del A/D, ¿ puede identificar el error ? Es posible que este error pasara desapercibido porque a efectos prácticos no se nota en la ejecución normal del programa, ¿ puede explicar porqué no se nota ?Estos dos errores están corregidos en versión de ‘interrupt.asm’ que acompaña al curso.

Por otra parte el programa está preparado para que el sentido de rotación de los LEDs cambie cuando se pulsa la tecla SW1 de la tarjeta de desarrollo. Sin embargo esto no funciona cuando el PICkit2 está controlado por MPLAB, y sin embargo funciona correctamente si está controlado por el programa PICkit2 Programmer; esto se debe a que el switch está conectado a RA3/MCLR/Vpp y esta línea, que es el Master Clear o reset, no queda liberada cuando usamos MPLAB. Puede comprobarse con un voltímetro que RA3 está continuamente a nivel lógico bajo y por ello no responde a la pulsación de la tecla.

33

Page 34: Microcontroladores PIC

Programa ‘Filter.asm’. Este programa ilustra el uso del llamado acceso indirecto a la memoria, para ello los PICsusan dos SFR que son accesibles en todos los bancos: FSR (04h) y INDF (00h), ‘File Select Register’ e ‘INDirect File’ respectivamente. FSR es lo que se conoce como un ‘puntero’ y no es más que una posición de memoria cuyo contenido nos indica donde accederemos al operar con INDF; es decir que si escribimos el valor 34h en FSR y luego leemos INDF, estaremos leyendo la celda de memoria 34h; en general cualquier operación que hagamos sobre INDF se realizará sobre la celda de memoria a la que apunte FSR. El banco al que accedemos viene determinado por “STATUS.IRP” que vale 0 para los bancos 0 y 1, y vale 1 para los bancos 2 y 3; la razón por la que sólo se necesita 1 bit de selección de banco es que ahora el puntero (FSR) tiene 8 bits, en vez de los 7 que se codifican en las instrucciones de acceso a memoria ‘normales’, y por tanto el bit más alto de FSR es el que selecciona si accedemos al banco 0 o al 1 si IRP=0, o al 2 o al 3 si IRP=1. Por ejemplo el siguiente código pone a 0 una serie de posiciones de memoria:

MOVLW 0x20 ;initialize pointerMOVWF FSR ;to RAM

NEXT CLRF INDF ;clear INDF registerINCF FSR ;inc pointerBTFSS FSR,4 ;all done?GOTO NEXT ;no clear next

Pregunta: ¿ cuantas posiciones de memoria inicializa este pequeño programa ? R:16

Observe las instrucciones de las líneas 36 a 39 como pueden reservarse varias posiciones contiguas en la memoria añadiendo ‘:x’ para reservar ‘x’ celdas de memoria dentro del bloque. Por ejemplo para acceder a las dos celdas reservadas con ‘Delay:2’, se puede usar ‘Delay’ y ‘Delay+1’ , y el preprocesador las sustituirá por las direcciones correctas.

Observe en el programa como se limpian las 8 celdas de memoria ‘Queue:8’ en la rutina FilterInit. Por otra parte en la rutina ‘Filter’ se almacena el dato de entrada (que viene en W) en la posición de memoria donde apunta FSR. El programa realiza el ‘filtrado’ sumando las últimas 8 lecturas y calculando su valor medio; note que la suma puede ser mayor de 8 bits, por lo que se almacena en un entero de 16 bits y para dividir por 8 se usan 3 pares de rotaciones, tal y como puede verse en las líneas 120 y siguientes. El resultado vuelve a tener 8 bits y se devuelve en W (y también en la variable Round).

Pregunta: Vemos en las líneas 102 a 104 como se resta un valor de 8 bits de la variable de 16 bits ‘RunningSum’; los PIC realizan la substracción como una adición del número complementario. ¿ Es correcto entonces el uso del bit de acarreo (Carry) que se hace en la línea 103 y 104 ? Razone la respuesta y ponga algún ejemplo numérico. R: Sí es correcto, si hay carry significa que la operación SUBWF no tuvo overflow. Ej. 3 – 1 se calcula como 3 + 255 = 2 + C; mientras que 1 – 3 se calcula como 1 + 253 = 254 (=-2) sin acarreo.

34

Page 35: Microcontroladores PIC

Programa ‘GreyCode.asm’. En este programa hacemos uso de una funcionalidad de los PICs que se conoce como GOTO computado (computed goto), esto es que se puede ejecutar un salto modificando el registro especial PCL que contiene la parte baja del contador de programa (PC); además el programa ilustra como se puede usar el registro PCLATH que nos permite modificar los bits altos del PC y que es necesario a la hora de hacer saltos (GOTO’s o CALL’s) a otras páginas de memoria distinta de la que estamos.

Tal y como puede verse en la figura, para el PIC16F690 que tiene una memoria de programa de 8 kwords, hace falta el PC tenga 13 bits; PCL contiene los 8 bits bajos y PCH (que *no* está mapeado en ningún registro) contiene los 5 bits altos. Podemos modificar PCH usando el SFR PCLATH, que se carga en PCH de forma automática al hacer cualquier operación en PCL. También si hacemos un salto o una llamada a subrutina los dos bits más altos de PCLATH se transfieren automáticamente a la parte alta de PC; de esta forma se pueden hacer saltos entre páginas de código.

En esta rutina lo que hacemos es un GOTO computado, sumándole a PCL el valor de entrada a la rutina que es W. La instrucción ‘retlw XX’ carga el valor del literal XX en W y retorna; en nuestro caso supongamos que la tabla empieza en la posición 81 (que coincide con el número de la línea) y que el valor de entrada a la rutina es W=3; entonces al ejecutar ‘addwf PCL,f’ el programa saltará a la línea 84 donde se retorna al programa principal con W cargado con el valor que se encuentra en la posición 3 de la tabla. El primer valor de la tabla se corresponde con el índice 0; en este caso la tabla tiene 16 elementos con el código Gray de los números del 0 al 15.Cuando el salto que se hace usando PCL pueda superar una frontera de 256*i, hay que modificar PCLATH antes de modificar PCL para que el salto se haga dentro de la tabla, si no podríamos saltar hacia atrás en vez de a la zona de la tabla; esto es lo que se muestra entre las líneas 71 y 79. Otra forma más simple de evitar esta complejidad es asegurarnos de que la tabla no atraviesa ninguna frontera de 256 bytes, usando ORG 0x100 por ejemplo. Aquí también se ilustra como leer el byte alto y bajo de una dirección de programa usando las palabras reservadas ‘high’ y ‘low’.

Este es el último de los programas ejemplo que acompañan al PICkit2, a partir de ahora veremos ejemplos desarrollados por el autor, que son un poco más complejos pero también más cercanos a aplicaciones prácticas.

35

Page 36: Microcontroladores PIC

El programa ‘1digito.asm’ ilustra como se pueden usar las tablas para representar los números del 0 al 15 en formato hexadecimal. En la tarjeta de desarrollo modificada por el autor tenemos 2 displaysde 7 segmentos conectados al puerto C; los 7 segmentos a,b,c,d,e,f, y g están conectados a través de resistencias a RC0, RC1, …, RC6 respectivamente y el punto decimal está conectado al bit 7 (RC7). El cátodo común de los displays está conectado al colector de sendos transistores cuyas bases están controladas por el puerto B<6:7>; siendo la línea 6 (RB6) el dígito menos significativo. La figura de la diapositiva siguiente representa un esquema similar (aunque no igual). Esta conexión permite la multiplexación de la representación de cada nibble secuencialmente (Ver diapositiva siguiente.)

En este programa contamos las pulsaciones de la tecla, en la variable ‘Counter’, igual que se hizo en el ejemplo ‘rebotes.asm’. En la tabla ‘SevenSeg’ convertimos el valor de la cuenta en el código para su representación en formato de 7 segmentos y de esta manera no necesitamos usar un descodificador del tipo 7447 o 4511 y además nos permite extender la funcionalidad de éstos que sólo pueden representar del 0 al 9. La tabla es fácil de crear, por ejemplo para el número ‘1’ tendremos que encender los segmentos b y c, por tanto tendremos que activar las líneas 1 y 2 del puerto C cuyo valor seria: 0000 0110b ó en hexadecimal 0x06 que es la entrada que vemos en la segunda línea de la tabla que corresponde al número ‘1’, ó en la figura para el ‘5’: 0110 1101 = 0x6D.

En la línea 32 activamos el transistor de la cifra baja de las dos que tenemos, y queda activado continuamente durante todo el programa, por lo tanto cuando activamos el puerto C con un valor de la tabla aparecerá el dígito correspondiente en la cifra derecha del display; dado que RB7 queda desactivado siempre la cifra alta nunca se activa.

Pregunta: ¿ Qué sucede si eliminamos las líneas 35 y 42 donde se llama a la rutina SevenSeg ? ¿ Veremos algo reconocible en el display ? R: No; veremos que se encienden unos segmentos difíciles de interpretar.

Observe como en las líneas 70 a 73 comprobamos que la tabla no atraviesa ninguna frontera de página y por tanto podemos hacer el GOTO computado sin mayor complicación (Comparar con la rutina de la diapositiva anterior). Estas líneas no generan ningún código ya que son sólo directivas para el preprocesador; si al compilar obtenemos el error ‘Tabla de SevenSeg cruza una frontera de página’ tendremos que mover la rutina a otro sitio del programa, normalmente esto no supone ningún problema.

36

Page 37: Microcontroladores PIC

El programa ‘2digitos.asm’ es similar al anterior: va contando pulsaciones de la tecla en la variable ‘Counter’, pero ahora usamos los dos dígitos del display de 7 segmentos. Dado que los dígitos están conectados en paralelo al puerto C tenemos que multiplexar, activando cada dígito durante un cierto tiempo y luego hacemos la misma operación con el otro dígito. Si estas operaciones se realizan con la suficiente rapidez, no se aprecia ningún parpadeo tan solo veremos que la luminosidad es menor que cuando se activaba un dígito permanentemente.

Para conseguir esto hemos desarrollado la rutina ‘Representa’, que recibe como parámetro de entrada el valor del registro de trabajo (W), y desde esta rutina se llama dos veces a ‘SevenSeg’, una para los 4 bits bajos (nibble bajo) y otra para el nibble alto. Además se llama a la rutina ‘TiempoEncendido’ para mantener el dígito durante unos 30 us encendido. Para que esta rutina funcione correctamente tenemos que hacer llamadas a ella de forma continua, ya que si no observaríamos un molesto parpadeo o incluso los dígitos podrían desaparecer si hubiese un periodo del orden de una décima de segundo sin que se activaran. Vemos pues que mientras el programa espera la pulsación de la tecla siempre tiene la llamada a ‘Representa’ dentro del bucle de comprobación, tal y como puede verse entre las líneas 41 a 45 del programa.

Introducimos en este programa también la directiva ‘DT x’ que es equivalente a ‘retlw x’, pero que nos permite poner las tablas más o menos grandes de una forma mucho más compacta tal y como puede verse entre las líneas 74 a 78. El uso de esta directiva (o de ‘retlw’) *no* se recomienda para los PIC de gama alta, ya que para éstos las instrucciones son de 16 bits pero el PC cuenta bytes, por lo que las tablas serían mayores y hay que hacer otras operaciones con PCL; además para la gama alta existen otras instrucciones que trabajan directamente con las tablas: TBLRD* y TBLWT*, que lee o escribe en la posición apuntada por TBLPTR que es un SFR triple con 21 bits útiles.

37

Page 38: Microcontroladores PIC

El programa ‘7segment.asm’ usa los dos dígitos del display de 7 segmentos de forma completa, para representar los 10 bits del resultado de la conversión A/D. Además ilustra la forma de hacer que la conversión se realice mientras el reloj principal del microcontrolador está parado (modo SLEEP), y como consecuencia el resultado es menos ‘ruidoso’. Se propone como ejercicio comprobar la mejora del ruido de conversión; simplemente comentar la línea 68 (SLEEP) y quitar el comentario de las líneas 70 a 76.

Se ha modificado la rutina ‘RepresentaAD’ para que use los puntos decimales de los 2 displays para representar los dos bits altos de los 10 bits del resultado del conversor A/D, y los 8 bits bajos se presentan en hexadecimal en las dos cifras del display. Así por ejemplo podemos tener en el display ‘ 9.A. ’ lo que significa 0x39A, o ‘ E 2. ’ lo que significa 0x1E2.

Observe también en el programa como se configura el PIC para habilitando las interrupciones de periféricos (PEIE), pero no habilitamos las interrupciones en general (GIE=0). Esto puede parecer inútil pero nos permite ‘despertar’ al micro cuando el A/D acaba la conversión, sin necesidad de tener una rutina de servicio de interrupciones. Este código realiza esta función:

movlw b'01110000‘ ;F(RC) para poder usar SLEEP

movwf ADCON1

banksel PIE1

bsf PIE1,ADIE

banksel INTCON

bsf INTCON,PEIE; activa INT del A/D para despertar del SLEEP

bcf INTCON,GIE; desactiva INT general para que no salte a 0004

38

Page 39: Microcontroladores PIC

El programa ‘7seg_interr.asm’ hace lo mismo que el anterior, pero esta vez usando las interrupciones del TMR0 y del conversor A/D. Vemos en la diapositiva la rutina de servicio de interrupciones (ISR): las líneas 32 a 34 salvan el contexto del programa principal; las líneas 35 a 40 determinan la fuente de la interrupción y saltan a la parte específica para cada periférico, en nuestro caso del TMR0 en las líneas 42 a 59, o del A/D en las líneas 68 a 83; después retoma el contexto del programa principal entre las líneas 60 a 64, para finalmente volver al programa principal re-activando las interrupciones con la orden RETFIE en la línea 65.

Puede observarse en el programa que activamos el modo SLEEP para hacer la medida del voltaje analógico sin interferencias; es muy importante en este caso notar que durante la conversión también TMR0 se para, ya que está ligado al oscilador principal que también deja de oscilar. Por tanto si estuviéramos haciendo una cuenta de tiempo basado en TMR0 se acumularían retrasos, y si cometiéramos el error de activar el modo SLEEP sin arrancar la conversión, el microcontrolador se pararía indefinidamente ya que ningún periférico generaría interrupciones en ese caso. Si se necesitara mantener un reloj y usar el modo SLEEP, se podría usar el Timer 1, que posee un oscilador independiente, en el que normalmente se coloca un cuarzo de 32 kHz, tal y como se representa en la parte superior de la diapositiva, que permite mantener un reloj de tiempo real, independiente del oscilador principal y del modo de trabajo usado en todo momento.

39

Page 40: Microcontroladores PIC

El programa ‘PWM.asm’ ilustra como se puede usar el módulo ECCP+ (EnhancedCapture/Compare/PWM+) del PIC 16F690 para controlar el brillo de los LEDs o cualquier otra función pseudo-analógica conectada a los pines 2, 3, 4 ó 5 del puerto C. La resolución del PWM es como máximo de 10 bits, y usa Timer2 (8 bits), más dos bits del pre-scaler nos permite controlar el ancho del pulso y el periodo; la relación pulso/periodo es el ciclo de trabajo o duty-cycle. En la diapositiva se puede ver una aplicación half-bridge estándar, half-bridge controlando un circuito full-bridge, y una aplicación full-bridge usando las 4 salidas del PIC; en este último caso el PIC puede introducir un tiempo muerto entre las activaciones de dos transistores en la misma columna.

Puede observarse que el número de SFRs que es necesario usar es bastante grande incluyendo los relativos al timer2: PIR1, TMR2, T2CON, CCPR1L, CCPR1H, CCP1CON, PWM1CON, PIE1, PR2, etc. La estructura y uso de estos registros se puede consultar en la datasheet del dispositivo; aunque dada la complejidad asociada se recomienda que para usar el módulo PWM se usen las librerías de mikroC ya que éstas simplifican notablemente el trabajo; más adelante veremos un programa en C con similar funcionalidad pero menos complejo.

El programa lee el conversor A/D y programa el duty cycle del PWM en función de la lectura; observamos que el brillo de los LEDs 3 y 4 de la tarjeta de desarrollo varía de forma continua al mover el potenciómetro RP1; si queremos controlar un dispositivo que requiera más potencia tendremos que usar un transistor como driver.Otra aplicación común del módulo ECCP+ es la de generar pitidos o alarmas; basta conectar un altavoz y variando la frecuencia y/o el ciclo de trabajo podremos cambiar el tono y el matiz del sonido de alarma.

Note que no podemos leer el A/D en modo SLEEP porque si se activa este modo se parará el oscilador principal, que es el origen del reloj del TMR2; por lo que en general cuando estamos usando el módulo PWM no podemos usar el modo SLEEP.

40

Page 41: Microcontroladores PIC

En diapositivas anteriores hemos mencionado determinadas opciones que sólo pueden establecerse cambiando la configuración del microcontrolador. Todos los PIC tienen una o varias palabras de configuración que sólo pueden ser escritas en el momento de la grabación (burn) del dispositivo. A veces a los bits de configuración se les llama fusibles (fuses) por su similitud con los chips PAL (Programmable Array Logic) en los que efectivamente se ‘quemaban’ determinadas conexiones internas del dispositivo. En los PIC los bits de configuración pueden re-escribirse tantas veces como el programa, ya que pueden considerarse parte de dicha memoria de programa, y estarían localizados en posiciones más allá de la memoria de usuario donde se almacena el programa (dirección 2007h para el PIC16F690) y que sólo puede ser escrita en el momento de la programación del chip. Como hemos dicho para dispositivos con memoria FLASH los bits de configuración se pueden escribir tantas veces como el programa, por lo que el nombre de fusibles es poco afortunado.

Los bits de configuración se pueden programar (poner a ‘0’) o dejar no-programado (se lee como ‘1’) para seleccionar varias configuraciones del dispositivo tal y como puede verse en la diapositiva. Observe que podemos entre otras opciones, seleccionar el modo del reloj principal, habilitar el watchdog, el master reset MCLR o la protección del código o los datos escritos en el chip.

En otras posiciones de memoria de esta área tenemos también la identificación del tipo de dispositivo que normalmente es reconocido por las herramientas de escritura y gracias a ello se puede evitar escribir el código escrito para un tipo en un chip distinto.

Desde un punto de vista práctico las herramientas de escritura normalmente proporcionan un menú de opciones de configuración en función del chip que usemos; tanto en MPLAB (ver la figura) como con mikroC podemos seleccionar la configuración cuando definimos un fichero de proyecto que incluye el código fuente de nuestro programa.

41

Page 42: Microcontroladores PIC

Vemos aquí un programa en C (Led_blink.c) tal y como se ve en la ventana de mikroC PRO justo después de compilar correctamente; en la ventana de mensajes nos dice cuanta RAM y ROM usa el programa. Si tenemos conectado el EASYPIC a nuestro ordenador, presionando F11 se escribirá el programa en el PIC y empezará la ejecución de forma inmediata. Este programa ilustra algunas ventajas de la programación en C, frente a la programación en ensamblador. Desglosaremos en detalle este primer programa:

•Todo lo está entre /* y */ es un comentario que puede extenderse en varias líneas, igual que lo que sigue a //.•En todo programa C hay una función ‘main()’ que es el comienzo del programa.•Lo que está entre llaves ‘, sentencias; -’ es un bloque de sentencias. Al principio de un bloque puede aparecer la declaración de las variables y su tipo. A casi todos los efectos cuando creamos un bloque es sintácticamente equivalente a una sola sentencia.•Los tipos de variables en mikroC son: char, int, float, void, (y double). Los tipos enteros pueden tener prefijos como: short, long y unsigned. En la línea 7 declaramos la variable ‘n’ como un entero corto.•En la línea 8 tenemos la primera sentencia que genera código, hace todos los puertos de salida. Vemos que en una línea puede haber varias sentencias; análogamente podríamos tener una sola sentencia en varias líneas, ya que una sentencia no termina hasta que aparece el ‘;’.•En la línea 9 inicializamos los puertos. Note que no tenemos que hacer ningún cambio de banco, ya que el compilador se encarga de esa complicación por nosotros.•La línea 10 es necesaria para poder usar las líneas conectadas al A/D como digitales (ver datasheet).•Bucle “while (condición) ,sentencias;-”, mientras la condición sea verdadera se repite el bloque de sentencias.•Las condiciones se construyen con los operadores relacionales: >, >=, <, <=, ==, != . Las condiciones simples se pueden unir con los operadores lógicos: &&, || y ! que significan AND, OR y NOT respectivamente. En C cualquier expresión o asignación es falsa si su resultado es 0, y verdadera en caso contrario. Un error común entre principiantes es escribir “while (n=3) ….”, esa condición es *siempre* verdadera porque lo que hace es asignar el valor 3 a n y dado que el resultado es != 0, el bucle será un bucle sin fin; normalmente se quiere expresar “while (n==3) …”, que ejecutará el bucle mientras n valga 3.•En C es muy común expresar “x = x +1”, condensado como “x++”. Cuando el operador incremento “++” precede a la variable, el incremento se hace *antes* de ejecutar la sentencia, y si aparece como sufijo el incremento se hace después. Así por ejemplo si a=3 y ejecutamos “b=a++;”, b valdrá 3 y a 4; pero si ejecutamos “b=++a;”, b y a valdrán 4. Análogamente existe el operador decremento ‘--’.•El bucle “for (sentencia1; condición; sentencia3) , bloque -”, es equivalente a: “sentencia1; while (condición) { bloque; + sentencia3; -” . Note que tanto la sentencia1 como la sentencia3 pueden ser una serie de sentencias simples separadas por comas; por ejemplo: “for (k=7, n=0; n-k != 0; n++, k--) ,…-” •Delay_ms(x) es una función de librería que nos proporciona un retardo de ‘x’ milisegundos.•La construcción “if (condición) {bloque-verdadero} else {bloque-falso-” se ejecuta sólo uno de los bloques en función de la condición. Se pueden encadenar tantos “if … else if … else if …” como se quieran.•Los operadores sobre los bits (bitwise) son: <<, >>, ~, &, | y ^; siendo respectivamente desplazamiento a la izquierda, derecha, complemento, AND, OR, y XOR.

42

Page 43: Microcontroladores PIC

1. El tipo de las variables determina su tamaño en memoria y el rango en valores de la variable. ‘char’ tiene 8 bits y va de 0 a 255. ‘int’ es el tipo por defecto, tiene 16 bits y rango de -32768 a +32767; ‘short int’ o sólo ‘short’ tiene 8 bits y rango de -128 a +127; ‘long int’ o sólo ‘long’ tiene 32 bits y rango de -231 a +231-1. Para ‘unsigned short’, ‘unsigned’ y ‘unsigned long’ los rangos van de 0 a 255, 65535 y 4294967295 (=232-1) respectivamente. ‘float’ es un número en coma flotante de 32 bits y rango +-6.8 E+38 y para números pequeños +-1.17 E-38. ‘double’ en mikroC es idéntico a float, en otras implementaciones tiene 64 bits. ‘void’ sólo se usa para funciones y significa que la función no devuelve ningún valor o no tiene parámetros de entrada. ‘const’ es un modificador que indica al compilador que la variable no va a cambiar durante la ejecución. ‘volatile’ indica que la variable puede ser cambiada por procesos ajenos al programa (HW).

2. La directiva del pre-compilador ‘#define xx yy’ crea el parámetro xx con el valor yy. Un parámetro queda fijado en el momento de la compilación y no cambia nunca.

3. Todas las variables simples se almacenan en 1, 2 ó 4 celdas de memoria; un array es una serie de datos del mismo tipo que ocupan lugares contiguos en memoria, por ejemplo: int x[3]; declara x como un array de 3 elementos x*0+, x*1+ y x*2+ que ocupan un bloque de memoria de 6 bytes. En C ‘x’ es también un puntero constante a ese bloque de memoria; un puntero es una variable que contiene la *dirección* de un objeto (variable, array, matriz, cadena o estructura) en la memoria. En general se declara así: int *p; ‘p’ no es un entero sino un puntero a una variable de tipo entero. Si ahora ejecutamos p=x; x[0] es también *p, de estas dos maneras estamos accediendo a la misma posición en memoria, ya que ‘*’ como operador unario significa ‘contenido de’; análogamente x*1+ == *(p+1), y x*2+ == *(p+2). Por otra parte ‘&’ como operador unario sobre una variable significa ‘dirección de’ la variable, luego también es cierto que: &x*0+ == p. Un ‘string’ o cadena es un array de caracteres; toda cadena termina con el carácter ‘\0’, por ejemplo: chars*+=“hola”; es una cadena de 5 caracteres que son: ‘h’ ‘o’ ‘l’ ‘a’ ‘\0’, y ‘s’ es un puntero constante a la posición que ocupa la ‘h’. Una matriz es una tabla de datos: int tabla[2][5]; tiene 2 filas y 5 columnas.

4. Una estructura es un conjunto de datos agrupados, normalmente relacionados en el mundo real. Por ejemplo struct PERSONA { char Nombre[8], Apellidos[20]; int edad; } Pepe; define la estructura de datos Pepe de 30 bytes con dos cadenas y un entero. Normalmente es mejor definir un tipo de estructura genérico por ejemplo: typedef struct {char Nombre[8], Apellidos[20]; int edad; } PERSONA; y después podemos usar ‘PERSONA’ como un tipo definido por el usuario y declarar: PERSONA Pepe, *p; donde estaríamos declarando la estructura Pepe igual que antes, y también declaramos ‘p’ como un puntero a estructura de tipo PERSONA. Para acceder a los componentes de la estructura se usa ‘.’, de forma que Pepe.Nombre o Pepe.edad; también si hacemos: p=&Pepe; podremos acceder con : p->Nombre o p->edad.

5. En mikroC todos los SFRs se pueden acceder como variables o como estructuras compuestas de 8 bites (B0, B1,…,B7) o campos (F0,F1,…,F7). Así podemos escribir: PORTB|=0x80; o PORTB.F7=1; para poner a 1 la línea 7 del puerto B. También siempre podremos referenciar cualquier bit con nombre: INTCON.GIE=1;

43

Page 44: Microcontroladores PIC

1. Los operadores aritméticos (+ - * / %) operan sobre dos datos de tipo simple. El operador % nos da el resto de la división entera. En general el resultado es del tipo de mayor tamaño. Por ejemplo si tenemos: char c; int i,x; y hacemos: x=i+c; ‘c’ se convierte a entero antes de la operación y el resultado es un entero de 16 bits. A veces podemos especificar explícitamente la conversión de tipo usando la técnica de ‘typecast’ sobre una expresión de la forma ‘(tipo)(expresión)’, por ejemplo: x= i + (int)(c);

2. Análogamente los operadores orientados a bits o lógicos también operan sobre dos datos y son: rotar a la izquierda <<, rotar a la derecha >>, AND &, OR | y XOR ^; complementar ~ opera sobre un solo dato. Si tenemos short j=0xF1; int i=0x281; y hacemos X= j|i; X valdrá 0x2F1, ó i>>2 vale 0xA0.

3. C nos permite simplificar expresiones del tipo: x = x (operador) y; para cualquier operador aritmético o lógico, por su sintaxis equivalente: x (operador)= y; así tenemos que x *= 3; es equivalente a: x = x*3;

4. Los operadores incremento ‘++’ o decremento ‘--’ se aplican sobre variables de carácter entero o punteros, y según vayan como sufijo o prefijo realizan la operación después o antes de usar la variable. Estos operadores se usan mucho en C y permiten hacer sentencias muy concisas. Cuando se aplican sobre punteros, este pasa a apuntar al elemento siguiente (o anterior) en la memoria *independientemente* del tamaño del objeto al que apunta. Por ejemplo si declaramos: int *p; y p apunta a la posición de memoria 0x70, ++p apuntará a la posición 0x72 ya que el tamaño de un entero es de 2 bytes.

5. Los operadores condicionales permiten comparar dos expresiones aritmético-lógicas mientras que los operadores AND, OR y NOT (&& , || y !) operan con expresiones condicionales. La construcción: (condición) ? Expresión_1 : Expresión_2; devuelve la expresión 1 si la condición es verdadera y la 2 si es falsa. Así por ejemplo podemos hacer: max = (a > b) ? a : b;. En C toda expresión (incluyendo las aritméticas) puede considerarse una condición, en ese caso la condición es verdadera si el resultado de la expresión es distinto de 0 y falsa en caso contrario.

6. Las construcciones: if (condición) { bloque1 } else { bloque2}; while (condición) { bloque3 }; y for(sentencia1; condición; sentencia3) { bloque4 }; ya fueron descritas anteriormente. La construcción: do { bloque } while (condición); siempre se ejecuta al menos una vez ya que la condición sólo se comprueba al final de la ejecución del bloque. En un bloque repetitivo al ejecutar una sentencia ‘break;’ se produce la salida inmediata del bloque. La sintaxis de switch es: switch (espresión) , case valor1: sentencias; …; break; case valor2: sentencias; …; break; ……; default: sentencias; …; break; - y permite ejecutar una serie de sentencias en los distintos casos que se enumeren.

7. Las funciones son subrutinas que pueden recibir una serie de variables de entrada, realizan una serie de operaciones y pueden devolver un valor del tipo de la función. Antes de usar cualquier función, ésta debe haber sido declarada o definida. En C las funciones reciben copias de las variables de entrada, por tanto si una función cambia el valor de estas variables cambia sólo la copia, no la variable original; sin embargo si la función trabaja con el puntero a una variable puede cambiar el contenido de la variable original. mikroCprovee una gran cantidad de funciones agrupadas en librerías que nos permiten trabajar con todos los módulos de todos los PIC de forma sencilla, estas funciones están documentadas en la ayuda de mikroC. A veces la declaración de las funciones de librería requiere un fichero ‘header’ que se debe incluir en nuestro programa con la sentencia ‘#include <librería.h>’, como ‘math.h’, ‘string.h’, etc.

44

Page 45: Microcontroladores PIC

Este programa ‘Display7seg_int.c’ muestra el resultado de la conversión del A/D multiplexado en los dos dígitos del display de 7 segmentos, similar al programa en ensamblador ‘7seg_interr.asm’ que hemos visto anteriormente. Nótese que en el programa no hay ninguna referencia al modelo de PIC; para mikroC tenemos que declarar el modelo de PIC, la velocidad y cualquier parámetro de inicialización en un fichero de proyecto, en este caso ‘Display7seg_int.mcppi’. Observamos aquí el programa completo y vemos que al usar la función de librería ‘ADC_Read(int chan)’, la de ‘Delay_ms()’ y las capacidades del lenguaje C, ocupa menos de 30 líneas.En las líneas 11 y 12 tenemos la tabla de conversión de los dígitos de 0 a 15 para su representación en formato de 7 segmentos. Todas las variables que se declaran fuera de un bloque de sentencias son visibles en todas las funciones que siguen a la declaración, son pues variables globales; frente a las variables que sólo existen en un bloque llamadas locales.La interrupción es una función que no devuelve ningún valor, ni lógicamente acepta parámetros. Se activa con cada fin de cuenta del timer 0; entre las líneas 18 a 20 escribe el código de los 7 segmentos para cada dígito y lo mantiene hasta la siguiente interrupción.En el programa principal se lee el conversor A/D mediante la función de la librería ‘unsignedADC_Read(unsigned short canal)’ y usamos los 8 bits bajos para generar las cifras hexadecimales de los dígitos y usamos los dos bits superiores para activar los puntos decimales de los dos dígitos. De esta forma podemos representar los 10 bits del resultado de la conversión.

Una vez que hayamos compilado el programa, cargaremos el fichero ‘.hex’ con el PICkit2 y lo ejecutamos en el sistema de desarrollo modificado para comprobar su correcto funcionamiento.

Pregunta: ¿ Cómo se podría aumentar el brillo de los segmentos de los displays ?Existe la posibilidad de que la interrupción del TMR0 llegue entre las líneas 37 y 38, ¿ qué efecto tendría ? ¿ se podría evitar ? (Ver diapositiva siguiente)

45

Page 46: Microcontroladores PIC

Para aumentar el brillo en el programa anterior podemos cambiar la sentencia de la línea 22 ‘if(++digito > 8u) …’ por ‘if (++digito > 1u) …’; por cierto el sufijo ‘u’ significa unsigned y se pone por compatibilidad ya que digito es también unsigned, aunque en este caso no sea necesario; pero en caso de que el número sea mayor de 32767 (por ejemplo 35000) el compilador lo interpreta como un número negativo (!) ya que su primer bit es 1.¿ Podríamos hacer este cambio sin necesidad de recompilar el programa ? Sí, y es fácil gracias a las capacidades de mikroC, en particular podremos ver el código ensamblador generado por el compilador, e incluso los datos del fichero ‘.hex’ en el fichero de listado: Display7seg_int.lst; donde vemos que para la línea 22 del fichero C, en el código generado la posición 0x001F contiene la instrucción ‘SUBLW 8’ que es la que se realiza para hacer la comparación del alto nivel. Vemos también que el opcode de la instrucción es ‘0x3C08’, si en PICkit2 cambiamos el 8 por un 1 y re-escribimos el fichero hex en el PIC, podremos observar el cambio de brillo de forma inmediata.Otro efecto interesante es incrementar mucho dicho valor, por ejemplo cambiar a 0x3A0, entonces observaremos un molesto parpadeo ya que no se alcanza la velocidad suficiente para la multiplexación de los dígitos.

Otra aplicación muy útil del fichero de listado y del código ensamblador generado, es que nos permite conocer como implementa el compilador las sentencias de alto nivel, de donde podemos extraer algunas ideas; así como podemos usar los nombres de las variables tanto globales, locales, los parámetros de las funciones y las etiquetas para poder usarlas en bloques ensamblador que nosotros podemos insertar en el programa C. Para insertar código en ensamblador se usa la directiva ‘asm { sentencias_ensamblador … -’. Podemos necesitar usar el ensamblador en alguna rutina que queramos optimizar, o para ejecutar comandos que no existen en C, como por ejemplo: CLRWDT, SLEEP o SWAPF.

Respecto a la última pregunta de la diapositiva anterior, si la interrupción cae en medio de la actualización de los dígitos, tendremos la cifra baja de una conversión y la alta de la otra. Si necesitamos evitar esto tendríamos que deshabilitar las interrupciones durante la escritura de los ‘codigo7’ y volver a habilitarlas una vez terminada. Para la (des-)habilitación hacemos INTCON.GIE igual a 0 ó 1 respectivamente.

46

Page 47: Microcontroladores PIC

Este programa lee la salida de un sensor de humedad del tipo CHS-MSS de TDK, que produce una señal de voltaje relacionada con la humedad relativa de la forma: RH(%) = 100 * voltaje. En nuestro caso leemos el canal 2 del A/D con una referencia de 5V (que es el voltaje de alimentación); entonces tenemos: voltaje = x*5V/1024; siendo x el valor leído por el A/D de 10 bits, por tanto tendremos que: RH = 100 * x * 5/1024 = x *125/256; y luego representamos el valor de RH en el display de 7 segmentos. El trabajo con el display de 7 segmentos usando el TMR0 es idéntico al que se hacía en la diapositiva anterior por lo que no lo mencionamos aquí.Para procesar las medidas del sensor de humedad hemos creado la función ‘short ReadHumidity(short chan)’, que recibe como parámetro el canal en el que tenemos conectado el sensor de humedad y devuelve el valor de la humedad relativa en %.Vemos en la parte baja de la diapositiva como se usa la función y como escribimos en los códigos de 7 segmentos los valores de las decenas (RH / 10) y de las unidades (RH % 10).

Con objeto de mejorar la calidad de la medida, en cada llamada a ReadHumidity repetimos la conversión 40 veces y calculamos el valor medio. Las operaciones con enteros hay que hacerlas con cierto cuidado, revise las sentencias donde se hacen los cálculos, están hechas de distintas formas para ilustrar diferentes maneras de hacer las cosas. Observe como usamos los paréntesis, y como hacemos algunas operaciones por partes ya que por ejemplo si calculamos: ad0 *= 5/8; nos daría 0 (!!), ¿ puede explicar porqué ? R: 5/8 es cero en división entera.Asimismo a veces hacemos las divisiones por potencias de 2 como desplazamientos ya que esto es más rápido.Si hubiéramos usado un float para las operaciones intermedias podríamos haber escrito las expresiones directamente sin ningún problema (por ejemplo: k =(short)((float)ad0/40*125.0/256.0);), pero el uso de datos en coma flotante requiere mucho poder de cálculo y en general no se recomienda para los PIC de gama media. Los PIC de gama alta tienen un multiplicador hardware por lo que pueden realizar operaciones en coma flotante con mayor rapidez y eficiencia, y por ello si necesitamos realizar cálculos en coma flotante es mejor usar un PIC18Fxxx.

47

Page 48: Microcontroladores PIC

Los LCD (Liquid Crystal Display) son los módulos de presentación que más comúnmente se usan con los microcontroladores por su versatilidad y bajo consumo. Estos módulos suelen tener caracteres de matriz de puntos (5x7) lo que les permite presentar 192 caracteres alfanuméricos distintos, así como algunos símbolos. Su contraste puede variarse con un potenciómetro conectado a Vo como puede verse en la figura inferior izquierda, y muchos tienen una luz trasera que permite la legibilidad sin luz exterior.

Dado que casi todos los LCDs tienen un controlador HD44780 de Toshiba o compatible de otros múltiples fabricantes, existe un estándar de facto; de manera que casi todos los módulos LCDs comerciales tienen los mismos pines similar al de la figura superior izquierda. El significado de los pines es el siguiente:• Vdd y Vss son la alimentación (5V y GND respectivamente)• Vo la entrada de contraste• RS entrada de Register Selection (RS=0 dato, RS=1 instrucción)• R/W lectura o escritura• E enable, su flanco de bajada es la señal que empieza la lectura o escritura internamente• DB0-DB7 bus de datos, cuando se configura en modo 4-bits se usan sólo DB4-DB7. El bit

DB7 es también conocido como BF (Busy Flag) y es una salida del LCD que nos informa de que está ocupado.

• A-K ánodo y cátodo del diodo de iluminación trasera.

En la imagen de la derecha vemos el proceso de inicialización de un módulo LCD estándar cuando se configura en el modo 4-bits. En todo el proceso RS=R/W=0; ya que escribimos en registros de control. Para ver el significado de todos los bits en las instrucciones consulte la datasheet que acompañan a estas notas. Nota: para algunos fabricantes de LCD se cambian alguno de los códigos de inicialización. Observe que todos los comandos del último bloque están compuestos en realidad de 8 bits que se introducen en dos tandas de 4 cada una.

48

Page 49: Microcontroladores PIC

Este programa es relativamente complejo y explota las capacidades del compilador de mikroC, por lo que el lector no debe preocuparse si le cuesta entenderlo. Hemos extraído aquí dos bloques del programa; uno es la definición de una serie de macros que describen como se ha conectado las líneas de control (E, R/W y RS) y datos (DB4-DB7) del display LCD al microcontrolador. Además ilustramos como se pueden construir macros usando otras macros previamente definidas. En realidad estamos mapeando las conexiones del LCD y podremos escribir las rutinas de control del LCD sin hacer referencia a ningún puerto concreto, y por tanto re-usables en cualquier otro programa previa re-definición sólo de las macros que definen las conexiones hardware. En general la re-utilización de código probado es una medida inteligente ya que elimina la duplicidad del trabajo y evita problemas antes de su aparición.

Para usar el LCD se han desarrollado una serie de rutinas que siguen las reglas de uso de los LCDs estándar: LCD_Read(), EsperaSiBusy(), LCDWriteNibble(), LCD_WriteByte(), InitLCD(), y printLCD().En la diapositiva vemos como ejemplo la rutina LCDWriteNibble(i), que introduce el nibblealto del parámetro de entrada ‘i’ al LCD. En nuestro caso la primera línea es equivalente a: TRISC = TRISC & 0x0F; ya que DataBusDir=TRISC y ~(NIBBLE)=0x0F; o sea hace las líneas 4 a 7 del puerto C líneas de salida. Luego pone a 0 la línea de R/W para escribir, espera unos microsegundos y activa la línea Enable. Después observamos como se puede usar la directiva ‘#if …… #endif’ del pre-procesador; las líneas del bloque sólo se compilarán si la condición es cierta, en caso contrario se ignoran. Vemos en el bloque la directiva ‘asm ,…-’ para introducir sentencias en ensamblador en el código C, donde usamos la referencia a la variable ‘i’ del C como ‘FARG_LCDWriteNibble’ en ensamblador. Siempre que usemos la directiva ‘asm’ con variables del C es necesario comprobar que el programa realiza la función correcta chequeando la salida del fichero ‘*.lst’ que mencionamos anteriormente. Si estamos usando la parte baja de un puerto permutaremos los nibbles del byte de entrada; luego ponemos el dato en el bus sin cambiar ninguno de los otros bites del DataBus, y después de una pequeña espera activamos el flanco de bajada de LCD_E que completa la operación de escritura. Observe como esta rutina obedece la especificación del proceso de escritura representado en la figura.

49

Page 50: Microcontroladores PIC

Este fragmento del programa ilustra también el uso de los parámetros definidos en la diapositiva anterior. En la rutina InitLCD() vemos también el uso de las rutinas LCDWriteNibble(short byte) y de LCDWriteByte(short byte, short modoRS). Vemos que la secuencia de inicialización se ha introducido en un array InitSequence[] que inicializamos en su declaración, donde hay un bloque de 4 elementos de los que sólo usamos el nibble superior y 6 siguientes en los que usamos el byte completo; estos dos bloques están separados por el número 0, que actúa como un flag y no se envía al LCD. A los valores de inicialización se ha añadido el byte 0x80 que posiciona el cursor de datos en la posición 0 (ver la datasheet del LCD), i.e. posicionamos el cursor en la esquina superior izquierda del LCD.

La rutina LCDWriteByte(short i, short modo) envía el dato ‘i’ en dos llamadas a LCDWriteNibble() en el modo especificado, que puede ser instrucción o dato. Note el uso que hacemos de el comando ensamblador ‘swapf’, que aunque no es estrictamente necesario es eficiente; asimismo el nombre de la variable C ‘i’ lo extraemos por inspección de la salida ensamblador que nos proporciona mikroC. También vemos algunas líneas del main() donde se declaran las cadenas texto1 y texto2, y vemos como se puede compilar de manera distinta en función del modelo de PIC con el que trabajamos. La rutina printLCD(short linea, char *texto) escribe el texto en la línea pedida. Observe como se puede usar el puntero local ‘texto’ para recorrer la cadena carácter a carácter, y esto no cambia nada en la rutina main() desde la que se hace la llamada.

Como norma general conviene buscar nombres significativos tanto de las variables como de las rutinas, ya que el propio nombre nos orienta sobre la función de la rutina, haciendo más fácil la comprensión del programa. Cuando se estime conveniente es bueno complementar determinadas sentencias o bloques con comentarios. Es también aconsejable mantener el tamaño de las rutinas relativamente pequeño, de 20 a 30 líneas; y hacer rutinas que llamen a otras rutinas más simples, o sea usar un paradigma tipo divide y vencerás. La gran mayoría de las rutinas que vemos aquí se podrán usar sin modificaciones en cualquier otro programa, siendo ésta otra de las grandes ventajas de la programación estructurada que nos permite desarrollar el lenguaje C. Recuerde sin embargo que en los PIC no se pueden llamar más de 8 rutinas encadenadas y que a veces dentro de cualquier rutina puede haber una llamada a la librería encubierta (por ejemplo al multiplicar); mikroC nos genera un fichero llamado LCD_rutinas_16F690_PORT-C.mcppi_callertable.txt que nos ayuda a comprobar esto.

Pregunta: el LCD que usamos tiene 2 líneas de 16 caracteres cada una, ¿ porqué entonces las cadenas texto1[17] reservan 17 bytes ? R: Porque hay que añadir el carácter terminador de cadena ‘\0’.

50

Page 51: Microcontroladores PIC

Este programa también usa las rutinas de control y escritura en el display LCD que hemos visto en las diapositivas anteriores para representar el valor de la humedad relativa en el display LCD. Hemos modificado la rutina ReadHumidity para que devuelva el valor de la humedad relativa en % y multiplicado por 100, para tener una medida más precisa sin necesidad de usar números en coma flotante. Esto es relativamente común ya que, como se ha mencionado anteriormente, los microcontroladores en general no disponen de mucha potencia de cálculo. Ilustramos aquí otra forma de llevar a cabo estas operaciones aritméticas dividiendo sólo por potencias de 2, lo que puede hacerse mediante rotaciones bit a bit que son muy rápidas.

Observe también como el programa principal hace uso de una serie de funciones de librería: WordToStr( unsigned, char *); strcpy(char *to, char *from); y strcat(char *, char *); mikroC provee una ayuda en línea muy completa sobre todas las funciones de librería. Las dos últimas son funciones generales de manejo de cadenas y en ANSI C se encuentran en la librería ‘string.h’; existen del orden de una veintena de estas funciones cuyo funcionamiento puede consultar en la ayuda del mikroC. La primera sin embargo es específica de mikroC y está contenida en la llamada librería de conversiones. En algunos casos no usaremos las funciones de librería y es más conveniente crear nuestras propias funciones para adaptarnos a los datos y formatos que necesitemos.

51

Page 52: Microcontroladores PIC

En este fragmento de programa vemos como las funciones de librería del mikroC facilitan enormemente el trabajo con la mayoría de los periféricos, en particular del módulo PWM. De este programa se ha eliminado toda la parte de inicialización de los puertos y del LCD ya que son idénticas a las del programa anterior. Tal y como puede verse en la figura tenemos conectado en la salida RC5 (P1A) un transistor MOSFET, entre cuyo drenador y la alimentación tenemos un motor de corriente continua y un diodo de protección. La salida PWM P1A se produce a través de la línea RC5 que también se usa en el LCD, por ese motivo tenemos que parar la salida del PWM cuando escribimos en el display LCD. Pruebe lo que sucede si no paramos la salida PWM.Inicializamos el módulo PWM a una frecuencia de 500 Hz usando la rutina de librería PWM_init(long fre) que escribe los valores necesarios en todos los SFRs del módulo PWM. Después usamos PWM_Change_Duty(short pwm); note que esta función ignora los dos bits bajos del PWM ya que sólo usa un entero corto. Si quisiéramos usar la máxima resolución del PWM tendríamos que hacernos nuestra propia rutina que escriba en los SFR CCPR1L y en CCP1CON<5:4> los dos bits menos significativos. Nota: dependiendo de la frecuencia de PWM seleccionada y de la del reloj del PIC, no siempre es posible alcanzar los 10 bits de resolución; ver en la datasheet para encontrar la información completa al respecto.

Vemos también aquí que en la segunda línea del LCD escribimos el voltaje que está alimentando al PIC cuyo valor medimos de manera indirecta, usando una referencia interna del PIC de 0.6 voltios. Si medimos este voltaje seleccionando el canal 13, referido a la Vddtendremos una lectura x = 0.6 * 1024/Vdd; así que en función de la lectura que obtengamos podemos calcular fácilmente Vdd, tal y como puede verse en el programa. Aquí de nuevo evitamos las operaciones en coma flotante y representamos el valor de Vdd*100 para evitar los decimales.

NOTA: las funciones PWM_init(), PWM_start(), etc. han sido renombradas en la última versión de mikroC a PWM1_init(), PWM1_start(), etc. Además PWM_Change_Duty es ahora PWM1_Set_Duty(). Consulte la ayuda de mikroC para más detalles.

52

Page 53: Microcontroladores PIC

En este programa vemos como se pueden usar las rutinas de mikroC PRO para trabajar con el display LCD, que usa el tipo específico de mikroC ‘sbit’ en conjunción con la palabra clave ‘at’, que no hace más que declarar un ‘alias’ a una variable. El tipo sbit se usa para definir la conexión hardware bit a bit del LCD a los puertos que usemos para esa función. La rutina LCD_init() necesita encontrar definidos los sbitLCD_RS, LCD_EN, …, LCD_D7; así como sus correspondientes LCD_RS_Direction, etc. que apuntan a los bits correspondientes en los registros TRISx. Observe que la conexión hardware del LCD en el EasyPIC tiene la línea RW=0, luego sólo podemos escribir, y por ejemplo no podemos chequear el bit busy del LCD. Una vez inicializado el display LCD podemos usar las funciones para enviar comandos al LCD LCD_Cmd(char comando); para escribir en cualquier posición LCD_Out(charfila, char columna, char *texto); o para escribir en la posición del cursor LCD_Out_Cp(char *texto). mikroC define una serie de constantes que permiten realizar operaciones de control sobre el LCD para limpiar la pantalla (_LCD_CLEAR), ocultar el cursor (_LCD_CURSOR_OFF), hacer scroll a la derecha (_LCD_SHIFT_RIGHT) e izquierda (_LCD_SHIFT_LEFT) , etc. Puede consultar la lista completa en la ayuda del mikroC o comprobar los códigos necesarios en el datasheetde los LCDs que acompaña al curso.

Observe también que en la declaración de las cadenas ‘txt4’ se declara de forma diferente, aunque todas las formas son equivalentes. Este programa está extraído directamente de la ayuda de mikroC y tiene un pequeño error en los comentarios de los dos últimos bucles for. ¿ Puede decir cual es ? R: se hace scroll 8 veces no 7

53

Page 54: Microcontroladores PIC

Este programa permite contar pulsos de overflow del TIMER-1 y presentar hh:mm:ss, así como el número de veces que el TIMER-1 ha generado una interrupción. Si se pulsa la tecla RB0 detiene el display de la cuenta de tics; si se pulsa RB7 presenta el número de microsegundos transcurridos. El pre-escalado se pone a 1, por lo que el timer-1 va a Fclk/4 y cada 2^16 pulsos se genera una interrupción que incrementa el lapso de tiempo transcurrido. Lapso es un unsignedlong que nos da un máximo de:

Tmax = 2^32 /(8 MHz /4 /2^16) = 140.7 E+06 seg = 1629 días

Si transcurrido un tiempo t (segundos) el lapso de tiempo contabilizado es diferente, se deberá a que la frecuencia del reloj no es exactamente 8MHz sino:

t = lapso /(8 MHz /4 /2^16) --> f = lapso*2^18/t

En las primeras sentencias usamos ‘typedef’ para definir tipos de datos que usaremos frecuentemente. Observe que la variable ‘Lapso’ es ‘volatile’, esto informa al compilador que esta variable puede ser cambiada en cualquier momento por la interrupción.

El diagrama representa el TIMER-1 de un PIC de gama alta 18F1320, que puede acceder los dos bytes TMR1H y TMR1L sin preocuparse por el posible rebosamiento del L al H; para los de gama media habría que chequear los valores leídos o al escribir parar el reloj del temporizador durante la operación.

La función QuitaCeros(char *p) de la diapositiva no siempre funciona, ¿ podría decir cuando falla ? Puede ver como se puede corregir el error en la versión del programa que acompaña al curso. R: falla cuando la cadena es “0”.

54

Page 55: Microcontroladores PIC

Una de las capacidades más potentes de los PIC es su capacidad de correr los programas tanto en ensamblador como en C en el modo debugger; lo que significa que podemos ejecutar el programa en tiempo real y a la vez instalar puntos de paro en el programa (breakpoints) y revisar el valor de cualquier variable, SFR o posición de memoria. En este programa ‘Teclado.c’ se ha desarrollado una rutina para manejar un teclado de 4x4 que devuelve el código ASCII de la tecla pulsada. Desde el punto de vista de programación la rutina teclado() es correcta, pero sin embargo cuando ejecutamos un sencillo programa de prueba no funciona, ¿ porqué ? Investiguemos …

En el esquema de la diapositiva vemos como funciona el teclado, que en nuestro caso controlamos con el puerto C; las líneas C4 a C7 se usan como drivers de cada una de las filas y las líneas C0 a C3 nos permiten determinar si alguna de las 4 teclas de la fila activada está pulsada; se asume que las entradas tienen una resistencia de pull-down a tierra. Para comprobar la rutina, debemos activar la capacidad de ejecución paso a paso: pinchamos en ‘Project Settings’ y activamos el ‘ICD Debug’, ya que tenemos que re-compilar el programa. Después hacemos ‘run-> Start Debugger F9’ y con esto se ejecuta el programa bajo nuestro control; podremos ejecutar paso a paso, entrar en las rutinas, o pasar sobre ellas, activar puntos de paro, ver el valor de cualquier variable, etc.

Ejecutando paso a paso veremos que la rutina funciona correctamente, pero a velocidad normal tenemos errores. En general a la hora de hacer chequeos siempre podemos introducir algunas sentencias que nos indiquen lo que está pasando durante la ejecución del programa, tal y como puede verse en las líneas marcadas en rojo. Con ellas representamos el valor leído del puerto y el número de teclas detectadas en la línea inferior del LCD. Llama la atención que cuando tenemos pulsada la tecla 1 se detecten 2 teclas pulsadas (la 1 y la 4), mientras que la última fila funciona bien. ¿ Puede encontrar una solución ? Se deja como ejercicio, en cualquier caso al final del programa puede encontrar la rutina con dos soluciones posibles.Sirva este ejemplo como llamada de atención sobre el hecho de que en el ámbito de los microcontroladores, las conexiones y desconexiones pueden ocurrir en intervalos de tiempo del orden del us y esto está fuera de nuestra percepción ‘normal’ de las cosas …

55

Page 56: Microcontroladores PIC

En este programa activamos el ‘perro guardián’ o watchdog del PIC; se trata de un temporizador de tipo RC independiente del reloj del microcontrolador. Para activarlo en los PIC de gama media debemos activar la opción correspondiente en los bits de configuración, para lo que tendremos que editar el proyecto. Una vez en ejecución el WDT genera un RESET si llega al final de su cuenta sin haber sido re-iniciado por el comando CLRWDT, de esta forma se puede detectar si el microcontrolador está ‘perdido’ esperando alguna respuesta o en un bucle sin fin; típicamente el tiempo de gracia es de 18 ms, pero sus márgenes mínimo y máximo son 7 a 33 ms. El WDT tiene la posibilidad de usar un pre-escaler (que está compartido con el TIMER0 !??!) que puede incrementar el tiempo de gracia hasta 128 veces, por lo que puede alcanzar un tiempo de unos 2 segundos típicamente. Para evitar que el WDT se agote hay que ejecutar frecuentemente CLRWDT en todos los procesos en los que el microcontrolador pueda emplear un tiempo mayor que el tiempo de gracia que tengamos establecido.

En el caso de este programa si mantenemos a nivel alto la línea PORTC0, el controlador entra en un bucle sin fin y se agotará el tiempo de gracia. Tal y como se ilustra en la figura, el PIC puede saber si se ha producido una parada chequeando el bit STATUS.NOT_TO, y dada la naturaleza estática de la memoria RAM no se pierde el valor de las variables. Esto es tremendamente útil, ya que podemos hacer programas muy inteligentes y resistentes a fallos durante la ejecución. Observe que la variable ‘i’ que cuenta el número de veces que se ha consumido el tiempo del WDT no se borra con el reset, aunque por otra parte por ejemplo sí que se modifican los valores de configuración de los puertos TRISx, por lo tanto deberemos resetear todos los dispositivos hardware cuya configuración haya sido cambiada por el reset.

Para los PIC de gama alta la activación del WDT se puede hacer durante la ejecución, así como su desactivación. El programa ‘test_WDT.c’ ilustra este diferente comportamiento introduciendo algunas líneas de compilación condicional.

56

Page 57: Microcontroladores PIC

Este programa se desarrolló para chequear un encoder RI-17 de ITEK. Este encoder tiene dos salidas en cuadratura llamadas A y B que conectamos al puerto C pines 0 y 1. Como puede verse en la figura las dos señales A y B determinan si la cuenta debe hacerse en un sentido o en otro. La cuenta incremental del encoder se incrementa o disminuye en función de cuando se produzca un flanco en alguna de las salidas A o B y del valor lógico que tenga la otra salida en el momento del flanco.Así contamos hacia arriba cuando:

A B---------- ---------

1 flanco-flanco- 0

0 flanco+flanco+ 1

Análogamente contamos hacia abajo cuando:

A B---------- ---------

0 flanco-flanco- 1

1 flanco+flanco+ 0

En el programa usamos el puerto D para controlar el display LCD usando las rutinas que ya hemos visto anteriormente. Hemos definido la variable ‘count’ de tipo long ya que la cuenta puede alcanzar 2^17 por vuelta en este encoder, por lo que un entero normal de 16 bits no es suficiente. Observe como se hace la discriminación para determinar si tenemos que contar hacia arriba o hacia abajo. También tenemos en cuenta la situación en que se haya producido cambio en A y B desde la última lectura, esto significa que el encoder se ha movido más de un paso y por tanto la cuenta pierde exactitud. Además note que mientras escribimos en el LCD no estamos leyendo por lo que este programa es puramente experimental; si necesitamos hacer uno funcional tendríamos que haber usado las interrupciones de cambio de estado.

57

Page 58: Microcontroladores PIC

Este es un programa de aplicación real para controlar una tarjeta acondicionadora de señal desarrollada en el exterior, en la que se encontraron varios errores hardware, además de errores en las señales de control generadas con un PIC16F877. La tarjeta acondicionadora tiene dos conjuntos de integradores, uno a 500 Hz y otro a 2 Hz; la salida del integrador pasa a un peak hold (detector de pico) y de ahí a un circuito S&H (Sample & Hold) que proporciona una salida continua. Cada grupo tiene tres señales de control comunes (siguiendo la nomenclatura de los esquemas hardware):

INT_500Hz e INT_2Hz -> son la señales que descargan los condensadores del integrador y del detector de picoPD1_500Hz y PD1_2Hz -> señales que mantiene el proceso de integración activoPD2_500Hz y PD2_2Hz -> señales que conectan la salida del detector de pico al S&H

Vemos en la figura estas tres señales de la rama 500Hz tal y como se ven en el osciloscopio, correspondientes al inicio del ciclo de integración, y que se repite cada 2 ms. En el programa dentro del bucle que se repite cada 2 ms tenemos la sentencia de limpiado del contador de watchdog ‘asm clrwdt’, de manera que si el programa se detiene por cualquier causa, el propio PIC se reinicializaría y volvería a generar las señales sin que el usuario notase nada más que un ciclo anómalo; esto hace que el programa sea muy robusto.Observe que dado que este es un programa de una aplicación real, está profusamente comentado con objeto de facilitar el mantenimiento del mismo. Nunca se acentúa suficientemente la necesidad de introducir buenos comentarios que complementan un buen programa.

58

Page 59: Microcontroladores PIC

Este es un programa de aplicación real y complejidad media/alta desarrollado para realizar una serie de medidas de temperatura, humedad, el voltaje de salida de unos fotodiodos y algunos otros voltajes necesarios para la monitorización de un equipo. El microcontrolador procesa todas las medidas y las envía a través de un puerto serie RS232 junto con un time-stamp. También acepta comandos simples que son:

A -> entra en modo auto, se envían todas las medidas cada segundot -> envía el time-stamp o tiempo trascurrido desde el arranqueThh:mm:ss -> establece la hora, minutos y segundos (sólo para presentación)I -> re-inicializa el hardare (A/D y TIMER-1)Cualquier otro carácter -> imprime los datos actuales

En la ventana derecha vemos como se inicializa el interface RS232, que requiere de un conversor de nivel de tipo MAX232 para convertir las señales TTL del micro a +-12 V de la norma RS232. En diapositivas siguientes veremos las rutinas de inicialización: InitStructs() e InitHardware(); de realización de las medidas: LeeAD(MEDIDA *); y de presentación: SprintMedida(char *, MEDIDA *).Observe como se ha definido la estructura MEDIDA, como un tipo definido por el usuario, y el array de estructuras X[], ya que tenemos 10 medidas diferentes. Para cada medida la estructura contiene: su valor v, un factor de calibración cal, unos márgenes de variación de la medida (Delta) Dpos y Dneg, un canal analógico asociado, un parámetro para filtrado por el método de Kalkman y una cadena nombre. Esta definición de la estructura pretende ser homomórfica, lo que significa que se parece a la realidad que describe; en el paradigma de la programación estructurada orientada a objetos, ese es uno de los criterios de calidad del software, ya que una buena definición de las estructuras de datos es el primer paso para un programa de calidad. Todos los nombres de las medidas estás definidos en el arrayNombre[NSIGNALS], ordenados según el número del canal analógico en el que se han conectado. La última se corresponde con una referencia de voltaje cuyo valor es Vref= 1.280 V; que se usa tanto para medir indirectamente la alimentación del PIC (Vcc), así como referencia para valores de voltaje por debajo de 1.2 V con objeto de aprovechar mejor la resolución del conversor A/D.

59

Page 60: Microcontroladores PIC

En esta diapositiva vemos las rutinas de inicialización de los datos del array de estructuras, y del hardware (A/D y TIMER-1). En el primer bucle for asignamos los canales correspondientes a cada medida; teniendo en cuenta que el canal 3 es el de la referencia alternativa y que el PIC18F2420 tiene los canales del 0 al 4 y del 9 al 12. Observe como distinguimos las medidas en función de los caracteres de su nombre. Después introducimos los valores de calibración que nos permiten hacer coincidir por ejemplo las medidas de temperatura de los dos sensores cuando los calibramos a la misma temperatura. El segundo bucle for introduce las constantes del filtro de Kalkman de primer orden para las temperaturas y las humedades relativas; para las demás medidas haremos las mediciones en crudo sin filtrar, ya que dichas medidas podrían tener variaciones más rápidas.

En la segunda rutina se configuran el A/D y el TIMER-1. La primera medida que hacemos es la de la alimentación Vcc, que se hace indirectamente midiendo el voltaje de la referencia. Después el Vcc medido se usa para tener medidas más precisas y coherentes con las medidas que hagamos usando la referencia de 1.28V como voltaje de referencia del A/D, valga la redundancia. En esta rutina usamos las funciones RS232Out(char *) y LeeAD(MEDIDA *) que describimos más adelante. Observe que siempre (re-)usamos la cadena declarada global MSG[24] lo que normalmente no se hace en C, ya que el tamaño de la memoria RAM de los microcontroladores es mucho más limitado que en los ordenadores normales en varios órdenes de magnitud (de GB a kB).

60

Page 61: Microcontroladores PIC

Esta es una de las rutinas más importantes del programa, ya que es la que realiza la lectura del A/D como su propio nombre indica. En general poner nombres adecuados para las rutinas y las variables mejora mucho la legibilidad del programa, reduciendo los comentarios necesarios, y por tanto mejora la calidad del software y facilita su mantenimiento.

Note que a esta función se le pasa como parámetro un puntero ‘m’ a la medida que vamos a hacer, y recordamos que en la estructura de datos MEDIDA teníamos todos los parámetros que determinan como realizar dicha medida y también su procesamiento. Con el primer ‘switch’ seleccionamos la referencia que vamos a usar en función de la primera letra del nombre, que referenciamos como: ‘*(m->Name)’; recuerde que ‘m->Name’ accede a la variable Name dentro de la estructura a la que apunta ‘m’; esa variable Name es a su vez un puntero a la cadena con el nombre de la medida, por lo que el operador unario ‘*’ accede a la posición de memoria de la primera letra de dicha cadena. Note que para las medidas de temperatura (T) siempre usamos la referencia de 1.28V, para los fotodiodos (P) usamos ésta o Vcc en función del valor de la última medida; y para todas las demás usamos siempre Vcc.En el caso de los fotodiodos también usamos el bit 4 del número del canal (que es < 15) como un flag que indica si la última vez usamos la referencia de 1.28V; el cambio de una referencia a otra se hace en los valores de 90% y 95% de Vref con histéresis.El bucle for que sigue hace NMED medidas, y vamos almacenando un sumatorio, la máxima y la mínima de las medidas. Luego como vemos en la segunda ventana, calculamos el valor medio y los incrementos hasta la medida máxima y mínima registradas (que nos dan una estimación de la estabilidad de la medida).Finalmente en el último switch hacemos la conversión del voltaje medido a la magnitud física que mide cada sensor; se le aplica el factor aditivo de calibración y procedemos al filtrado de Kalkman de primer orden. Un filtro de primer orden simplemente hace una medida, digamos x y calcula:

x(i+1) = a * x(i) + (1-a)*x; en nuestro caso a= m->kalk / KALKMAX

siendo ‘a’ el parámetro de filtrado 0<a<1, cuanto más cercano a 1 mayor será la magnitud del filtrado.

61

Page 62: Microcontroladores PIC

En la ventana izquierda tenemos una serie de funciones relacionadas con la presentación. Las dos primeras reciben y devuelven un puntero a una cadena; están pensadas para concatenar los elementos que forman un mensaje, tal y como podemos ven en la función Printahms(char *).QuitaCeros(char *) elimina los ceros de una cadena que representa un número y luego mueve las cifras significativas al principio de la cadena y devuelve un puntero al final de la cadena; así por ejemplo si recibimos un puntero a “00123\0”, lo convierte en “123\0”, y con el puntero que devuelve podremos seguir añadiendo lo que sea después del 3.sprint(char *p, uint32 k, short dig) escribe ‘dig’ dígitos del número ‘k’ en la cadena ‘p’, y devuelve un puntero al final de la cadena. Note que si en número tiene más de ‘dig’ cifras pone un ‘*’ para indicar overflow. El nombre de esta rutina imita al de ‘sprintf’ de la librería estándar del C que significa imprimir en una cadena con formato; esta función también se puede usar en mikro C con los PIC de gama alta, pero es una función muy potente por lo que consume mucha memoria y recursos.Printahms(char *) usa ‘sprint’ y construye la cadena hh:mm:ss usando las variables globales: hor, mi, seg.RS232out(char *) envía la cadena dada usando la función de librería UART1_Write(char) que escribe carácter a carácter.En la segunda ventana SprintFn(char *p,float x, short ndig) escribe el número ‘x’ con ‘ndig’ decimales en la cadena ‘p’. Empieza por el signo, luego la parte entera y la parte decimal usando ‘sprint’. Pregunta: ¿ por qué SprintFn no puede ( ni debe ) imprimir números mayores de 1.0E10 ? R: porque la parte entera es un long que tiene como máximo 9 cifras.Finalmente PrintimeStamp(void) envía una cadena tipo “ts= 45.123” al puerto serie. En la primera versión del programa se calculaba el número entero de segundos aparte; ahora tenemos el tiempo en una variable float, ¿ se podría simplificar esta rutina ? R: usando SprintFn(MSG+3,tstamp,3)

62

Page 63: Microcontroladores PIC

Con estas tres funciones completamos la descripción del programa:RS232in(char *p, ushort max) lee carácter a carácter lo que se recibe vía puerto serie hasta un máximo de ‘max’ caracteres o hasta que se pulsa enter, almacenando los caracteres recibidos en la cadena ‘p’. Devuelve el último carácter recibido ó 0 si no se recibió nada. Note como el bucle ‘for’ tiene un condición compuesta que permite terminar cuando se han recibido ‘max’ caracteres, o un enter, o se espera demasiado. Observe que sólo deberemos leer un carácter cuando se haya recibido, para esto usamos las funciones de librería UART1_Data_Ready() y UART1_Read(); una vez leído lo re-enviamos para confirmar su recepción, o sea hacemos eco.Sethora(char *) actualiza las variables globales h0, m0 y s0, según la cadena de entrada de la forma hh:mm:ss, e inicializa la cuenta de ticks del reloj. En esta rutina usamos la función ‘isdigit(char)’ que pertenece a la librería de C estándar (ctype.h en ANSI C) que devuelve 1 si el carácter es una cifra ente 0 y 9. Otras funciones similares son: isalpha(), islower(), isupper(), isxdigit(), isalnum(), etc. consulte la ayuda de mikroC para ver la referencia de estas funciones. Vea como convertimos cada bloque de dos caracteres al valor decimal que representan; también podríamos haber usado otra función del C estándar que es ‘atoi()’ cuya descripción, y la de algunas variantes, puede consultar en la ayuda de mikroC.SprintMedida(char *p, MEDIDA *m) escribe en la cadena ‘p’ el resultado de la medida a la que apunta ‘m’, incluyendo el nombre de la medida, su valor y el intervalo de los valores registrados en las lecturas múltiples que se realizan como vimos, en la función LeeAD(). Según el nombre de la medida, esta rutina determina el número de decimales apropiado; los valores del intervalo se dan siempre en ‘crudo’ y representan el ruido de la medida sobre el rango máximo del A/D que es 1024 (2^10). Así podemos tener “T1=23.445+05-03” lo que significa que el valor medio de la temperatura T1 es 23.445ºC, y en la tanda de medidas en crudo todas resultaron entre 5 unidades por encima y 3 unidades por debajo del valor medio.

63

Page 64: Microcontroladores PIC

Para terminar vemos en esta diapositiva el contenido del fichero de texto que genera mikroC con la tabla de llamadas. En un programa de la complejidad del que hemos analizado en las últimas diapositivas, es muy importante revisar este fichero y asegurarse de que el número máximo de llamadas anidadas que permita el PIC con el que estemos trabajando no se supera (8 para los PIC de gama media y 31 para la gama alta), ya que como se ha mencionado anteriormente, si esto sucede el sistema funcionaría incorrectamente sin dar ninguna información de fallo, y puede ser muy difícil determinar el origen del problema.

Vemos en la tabla que tenemos hasta 7 niveles de llamadas, muchas de ellas son funciones que nosotros no hemos definido, ya que pertenecen a la librería de mikroC, pero que igualmente consumen su correspondiente nivel en la pila de direcciones de retorno.

Conviene recordar aquí que cuando estamos usando las interrupciones, debemos incrementar el número máximo de niveles que vemos en la tabla como mínimo en otro nivel más y si en la rutina de la interrupción hacemos más llamadas también deberemos sumar todos los niveles usados, puesto que la interrupción puede ocurrir en cualquier momento de la ejecución del programa.

Las imágenes que se muestran fueron obtenidas para la calibración de dos sensores de temperatura y otros dos de humedad. El sistema registró los valores medidos en una oficina *casi* imperturbada. Podemos ver que las medidas son muy coherentes y que la precisión es muy buena. El pico en la humedad se debe al trabajo de la limpiadora y los picos de la temperatura se deben a la presencia del autor cerca de los sensores chequeando el sistema.

64