Вы находитесь на странице: 1из 192

El mundo de los microcontroladores - Microcontroladores PIC –

Programación en C con ejemplos


 TOC
 Capitulo 1
 Capitulo 2
 Capitulo 3
 Capitulo 4
 Apéndice A
Capitulo 1: El mundo de los microcontroladores

La situación actual en el campo de los microcontroladores se ha producido


gracias al desarrollo de la tecnología de fabricación de los circuitos integrados.
Este desarrollo ha permitido construir las centenas de miles de transistores en
un chip. Esto fue una condición previa para la fabricación de un
microprocesador. Las primeras microcomputadoras se fabricaron al añadirles
periféricos externos, tales como memoria, líneas de entrada/salida,
temporizadores u otros. El incremento posterior de la densidad de integración
permitió crear un circuito integrado que contenía tanto al procesador como
periféricos. Así es cómo fue desarrollada la primera microcomputadora en un
solo chip, denominada más tarde microcontrolador.
 1.1 Introducción
 1.2 NÚMEROS, NÚMEROS, NÚMEROS...
 1.3 DETALLES IMPORTANTES
 1.4 MICROCONTROLADORES PIC
1.1 INTRODUCCIÓN

Los principiantes en electrónica creen que un microcontrolador es igual a un


microprocesador. Esto no es cierto. Difieren uno del otro en muchos sentidos.
La primera y la más importante diferencia es su funcionalidad. Para utilizar al
microprocesador en una aplicación real, se debe de conectar con componentes
tales como memoria o componentes buses de transmisión de datos. Aunque el
microprocesador se considera una máquina de computación poderosa, no está
preparado para la comunicación con los dispositivos periféricos que se le
conectan. Para que el microprocesador se comunique con algún periférico, se
deben utilizar los circuitos especiales. Así era en el principio y esta práctica
sigue vigente en la actualidad.
Por otro lado, al microcontrolador se le diseña de tal manera que tenga todas
las componentes integradas en el mismo chip. No necesita de otros
componentes especializados para su aplicación, porque todos los circuitos
necesarios, que de otra manera correspondan a los periféricos, ya se
encuentran incorporados. Así se ahorra tiempo y espacio necesario para
construir un dispositivo.

¿QUE PUEDEN HACER LOS MICROCONTROLADORES?

Para entender con más facilidad las razones del éxito tan grande de los
microcontroladores, vamos a prestar atención al siguiente ejemplo. Hace unos
10 años, diseñar un dispositivo electrónico de control de un ascensor de un
edificio de varios pisos era muy difícil, incluso para un equipo de expertos. ¿Ha
pensado alguna vez en qué requisitos debe cumplir un simple ascensor?
¿Cómo lidiar con la situación cuando dos o más personas llaman al ascensor al
mismo tiempo? ¿Cuál llamada tiene la prioridad? ¿Cómo solucionar las
cuestiones de seguridad, de pérdida de electricidad, de fallos, de uso indebido?
Lo que sucede después de resolver estos problemas básicos es un proceso
meticuloso de diseñar los dispositivos adecuados utilizando un gran número de
los chips especializados. Este proceso puede tardar semanas o meses,
dependiendo de la complejidad del dispositivo. Cuando haya terminado el
proceso, llega la hora de diseñar una placa de circuito impreso y de montar el
dispositivo.¡Un dispositivo enorme! Es otro trabajo difícil y tardado. Por último,
cuando todo está terminado y probado adecuadamente, pasamos al momento
crucial y es cuando uno se concentra, respira profundamente y enciende la
fuente de alimentación.

Esto suele ser el punto en el que la fiesta se convierte en un verdadero trabajo


puesto que los dispositivos electrónicos casi nunca funcionan apropiadamente
desde el inicio. Prepárese para muchas noches sin dormir, correcciones,
mejoras... y no se olvide de que todavía estamos hablando de cómo poner en
marcha un simple ascensor.

Cuando el dispositivo finalmente empiece a funcionar perfectamente y todo el


mundo esté satisfecho, y le paguen por el trabajo que ha hecho, muchas
compañías de desarrollo estarán interesadas en su trabajo. Por supuesto, si
tiene suerte, cada día le traerá una oferta de trabajo de un nuevo inversionista.
Sin embargo, si lo requieren para trabajar en el control de los elevadores de un
nuevo edificio que tiene cuatro pisos más de los que ya maneja su sistema de
control. ¿Sabe cómo proceder? ¿Cree acaso que se pueden controlar las
demandas de sus clientes? Pensamos que usted va a construir un dispositivo
universal que se puede utilizar en los edificios de 4 a 40 pisos, una obra
maestra de electrónica. Bueno, incluso si usted consigue construir una joya
electrónica, su inversionista le esperarará delante de la puerta pidiendo una
cámara en el ascensor o una música relajante en caso de fallo de ascensor. O
un ascensor con dos puertas.

De todos modos, la ley de Murphy es inexorable y sin duda usted no podrá


tomar ventaja a pesar de todos los esfuerzos que ha hecho. Por desgracia,
todo lo que se ha dicho hasta ahora sucede en la realidad. Esto es lo que
“dedicarse a la ingeniería electrónica” realmente significa. Es así como se
hacían las cosas hasta aparición de los microcontroladores diseñados -
pequeños, potentes y baratos. Desde ese momento su programación dejó de
ser una ciencia, y todo tomó otra dirección ...
El dispositivo electrónico capaz de controlar un pequeño submarino, una grúa o
un ascensor como el anteriormente mencionado, ahora está incorporado en un
sólo chip. Los microcontroladores ofrecen una amplia gama de aplicaciones y
sólo algunas se exploran normalmente. Le toca a usted decidir qué quiere que
haga el microcontrolador y cargar un programa en él con las instrucciones
apropiadas. Antes de encender el dispositivo es recomendable verificar su
funcionamiento con ayuda de un simulador. Si todo funciona como es debido,
incorpore el microcontrolador en el sistema. Si alguna vez necesita cambiar,
mejorar o actualizar el programa, hágalo. ¿Hasta cuándo? Hasta quedar
satisfecho. Eso puede realizarse sin ningún problema.
Sabía usted que todas las personas pueden ser clasificadas en uno de 10
grupos, en los que están familiarizados con el sistema de numeración binario y
en los que no están familiarizados con él. Si no entendió lo anterior significa
que todavía pertenece al segundo grupo. Si desea cambiar su estado, lea el
siguiente texto que describe brevemente algunos de los conceptos básicos
utilizados más tarde en este libro (sólo para estar seguro de que estamos
hablando en los mismos términos).
1.2 NÚMEROS, NÚMEROS, NÚMEROS...

¡La matemática es una gran ciencia! Todo es tan lógico y simple... El universo
de los números se puede describir con sólo diez dígitos. No obstante,
¿realmente tiene que ser así? ¿Necesitamos exactamente esos 10 dígitos? Por
supuesto que no, es sólo cuestión del hábito. Acuérdese de las lecciones de la
escuela. Por ejemplo, ¿qué significa el número 764? Cuatro unidades, seis
decenas y siete centenas. ¡Muy simple! ¿Se podría expresar de una forma más
desarrollada? Por supuesto que sí: 4 + 60 + 700. ¿Aún más desarrollado? Sí:
4*1 + 6*10 + 7*100. ¿Podría este número parecer un poco más “científico”? La
respuesta es sí otra vez: 4*100 + 6*101 + 7*102. ¿Qué significa esto
realmente? ¿Por qué utilizamos exactamente estos números 100, 101 y 102 ?
¿Por qué es siempre el número 10? Es porque utilizamos 10 dígitos diferentes
(0, 1, 2...8, 9). En otras palabras, es porque utilizamos el sistema de
numeración en base 10, es decir el sistema de numeración decimal.
SISTEMA DE NUMERACIÓN BINARIO

¿Qué pasaría si utilizáramos sólo dos números 0 y 1? Si sólo pudiéramos


afirmar (1) o negar (0) que algo existe. La respuesta es “nada especial”,
seguiríamos utilizando los mismos números de la misma manera que utilizamos
hoy en día, no obstante ellos parecerían un poco diferentes. Por ejemplo:
11011010.¿Cuántas son realmente 11011010 páginas de un libro? Para
entenderlo, siga la misma lógica como en el ejemplo anterior, pero en el orden
invertido. Tenga en cuenta que se trata de aritmética con sólo dos dígitos 0 y 1,
es decir, del sistema de numeración en base 2 (sistema de numeración
binario).
Evidentemente, se trata del mismo número representado en dos sistemas de
numeración diferentes. La única diferencia entre estas dos representaciones
yace en el número de dígitos necesarios para escribir un número. Un dígito (2)
se utiliza para escribir el número 2 en el sistema decimal, mientras que dos
dígitos (1 y 0) se utilizan para escribir aquel número en el sistema binario.
¿Ahora está de acuerdo que hay 10 grupos de gente? ¡Bienvenido al mundo de
la aritmética binaria! ¿Tiene alguna idea de dónde se utiliza?

Excepto en las condiciones de laboratorio estrictamente controladas, los


circuitos electrónicos más complicados no pueden especificar con exactitud la
diferencia entre dos magnitudes (dos valores de voltaje, por ejemplo), si son
demasiado pequeños (más pequeños que unos pocos voltios). La razón son los
ruidos eléctricos y fenómenos que se presentan dentro de lo que llamamos
“entorno de trabajo real” (algunos ejemplos de estos fenómenos son los
cambios imprevisibles de la tensión de alimentación, cambios de temperatura,
tolerancia a los valores de los componentes etc...). Imagínese una
computadora que opera sobre números decimales al tratarlos de la siguiente
manera: 0=0V, 1=5V, 2=10V, 3=15V, 4=20V... 9=45V!?

¿Alguien dijo baterías?

Una solución mucho más fácil es una lógica binaria donde 0 indica la ausencia
de voltaje, mientras que 1 indica la presencia de voltaje. Simplemente, es fácil
de escribir 0 o 1 en vez de “no hay voltaje” o “ hay voltaje”. Mediante el cero
lógico (0) y uno lógico (1) la electrónica se enfrenta perfectamente y realiza con
facilidad todas las operaciones aritméticas. Evidentemente, se trata de
electrónica que en realidad aplica aritmética en la que todos los números son
representados con sólo dos dígitos y donde sólo es importante saber si hay
voltaje o no. Por supuesto, estamos hablando de electrónica digital.

SISTEMA DE NUMERACIÓN HEXADECIMAL

En el principio del desarrollo de las computadoras era evidente que a la gente


le costaba mucho trabajar con números binarios. Por eso, se estableció un
nuevo sistema de numeración, que utilizaba 16 símbolos diferentes. Es llamado
el sistema de numeración hexadecimal. Este sistema está compuesto de 10
dígitos a los que estamos acostumbrados (0, 1, 2, 3,... 9) y de seis letras del
alfabeto A, B, C, D, E y F. ¿Cuál es el propósito de esta combinación
aparentemente extraña? Basta con mirar cómo todo en la historia de los
números binarios encaja perfectamente para lograr una mejor comprensión del
tema.

El mayor número que puede ser representado con 4 dígitos binarios es el


número 1111. Corresponde al número 15 en el sistema decimal. En el sistema
hexadecimal ese número se representa con sólo un dígito F. Es el mayor
número de un dígito en el sistema hexadecimal. ¿Se da cuenta de la gran
utilidad de estas equivalencias? El mayor número escrito con ocho dígitos
binarios es a la vez el mayor número de dos dígitos en el sistema hexadecimal.
Tenga en cuenta que una computadora utiliza números binarios de 8 dígitos.
¿Acaso se trata de una casualidad?

CÓDIGO BCD

El código BCD (Binary-Coded Decimal - Código binario decimal) es un código


binario utilizado para representar a los números decimales. Se utiliza para que
los circuitos electrónicos puedan comunicarse con los periféricos utilizando el
sistema de numeración decimal o bien utilizando el sistema binario dentro de
“su propio mundo”. Consiste en números binarios de 4 dígitos que representan
los primeros diez dígitos (0, 1, 2, 3...8, 9). Aunque cuatro dígitos pueden hacer
16 combinaciones posibles en total, el código BCD normalmente utiliza a las
primeras diez.
CONVERSIÓN DE SISTEMAS DE NÚMERACIÓN

El sistema de numeración binario es el que utilizan los microcontroladores, el


sistema decimal es el que nos resulta más comprensible, mientras que el
sistema hexadecimal presenta un balance entre los dos. Por eso, es muy
importante aprender cómo convertir los números de un sistema de numeración
a otro, por ejemplo, cómo convertir una serie de ceros y unos a una forma de
representación comprensible para nosotros.

CONVERSIÓN DE NÚMEROS BINARIOS A DECIMALES

Los dígitos en un número binario tienen ponderaciones diferentes lo que


depende de sus posiciones dentro del número que están representando.
Además, cada dígito puede ser 1 o 0, y su ponderación se puede determinar
con facilidad al contar su posición empezando por la derecha. Para hacer una
conversión de un número binario a decimal es necesario multiplicar las
ponderaciones con los dígitos correspondientes (0 o 1) y sumar todos los
resultados. La magia de la conversión de un número binario a decimal funciona
de maravilla... ¿Tiene duda? Veamos el siguiente ejemplo:

Cabe destacar que es necesario utilizar sólo dos dígitos binarios para
representar a todos los números decimales de 0 a 3. Por consiguiente, para
representar los números de 0 a 7 es necesario utilizar tres dígitos binarios, para
representar los números de 0 a 15 - cuatro dígitos etc. Dicho de manera
sencilla, el mayor número binario que se puede representar utilizando n dígitos
se obtiene al elevar la base 2 a la potencia n. Luego, al resultado se le resta 1.
Por ejemplo, si n=4:

24 - 1 = 16 - 1 = 15
Por consiguiente, al utilizar 4 dígitos binarios, es posible representar los
números decimales de 0 a 15, que son 16 valores diferentes en total.

CONVERSIÓN DE NÚMEROS HEXADECIMALES A DECIMALES

Para realizar una conversión de un número hexadecimal a decimal, cada dígito


hexadecimal debe ser multiplicado con el número 16 elevado al valor de su
posición. Por ejemplo:
CONVERSIÓN DE NÚMEROS HEXADECIMALES A BINARIOS

No es necesario realizar ningún cálculo para convertir un número hexadecimal


a binario. Los dígitos hexadecimales se reemplazan simplemente por los cuatro
dígitos binarios apropiados. Ya que el dígito hexadecimal máximo es
equivalente al número decimal 15, es necesario utilizar cuatro dígitos binarios
para representar un dígito hexadecimal. Por ejemplo:

MARCAR LOS NÚMEROS

El sistema de numeración hexadecimal, junto con los sistemas binario y


decimal, se consideran los más importantes para nosotros. Es fácil realizar una
conversión de cualquier número hexadecimal a binario, además es fácil de
recordarlo. Sin obstante, estas conversiones pueden provocar una confusión.
Por ejemplo, ¿qué significa en realidad la sentencia: “Es necesario contar 110
productos en una cadena de montaje”? Dependiendo del sistema en cuestión
(binario, decimal o hexadecimal), el resultado podría ser 6, 110 o 272
productos, respectivamente. Por consiguiente, para evitar equivocaciones,
diferentes prefijos y sufijos se añaden directamente a los números. El prefijo $
o 0x así como el sufijo h marca los números en el sistema hexadecimal. Por
ejemplo, el número hexadecimal 10AF se puede escribir así: $10AF, 0x10AF o
10AFh. De manera similar, los números binarios normalmente obtienen el sufijo
% o 0B. Si un número no tiene ni sufijo ni prefijo se considera decimal.
Desafortunadamente, esta forma de marcar los números no es estandarizada,
por consiguiente depende de la aplicación concreta.

La siguiente es tabla comparativa que contiene los valores de números 0-255


representados en tres sistemas de numeración diferentes. Esto es
probablemente la manera más fácil de entender lógica común aplicada a todos
los sistemas de numeración.

NÚMEROS NEGATIVOS

Como ya hemos visto, para escribir un número negativo en matemáticas, basta


con añadirle el prefijo “-” (signo menos). Sin embargo, en la programación, al
acabarse el proceso de la compilación, se quedan sólo los números binarios,
volviéndose las cosas más complicadas. Se utilizan sólo dos dígitos - 0 y 1,
mientras que todos los demás números, símbolos y signos se forman por
medio de las combinaciones de estos dos dígitos. En el caso de los números
negativos, la solución es la siguiente: En los números negativos, el bit más
significativo (el bit del extremo izquierdo) representa el signo del número
(donde 0 será positivo y 1 negativo). En el caso de un número de 8 bits, para
representar un valor numérico sólo quedan 7 bits. De acuerdo a este tipo de
codificación el número +127 es el mayor número positivo con signo que se
puede representar con 8 bits. Asimismo, hay cero (0) positivo y negativo
(refiérase a la tabla de la derecha). La siguiente pregunta sería: ¿Cómo es
posible saber de qué número se trata? Por ejemplo, si ponemos el número
10000001, ¿es -1 o 129? No se preocupe, de eso se encarga el compilador.
Ésta es la razón por la que se declaran variables al escribir el programa.
Bueno, de eso vamos a hablar a continuación.

BIT

La teorí a dice que un bit es la unidad básica de información...Vamos a


olvidarlo por un momento y demostrar qué es eso en la práctica. La respuesta
es - nada especial- un bit es un sólo dígito binario. Similar a un sistema de
numeración decimal en el que los dígitos de un número no tienen la misma
ponderación (por ejemplo, los dígitos en el número 444 son los mismos pero
tienen los valores diferentes), el “significado” de un bit depende de la posición
que tiene en número binario. En este caso no tiene sentido hablar de unidades,
centenas etc. en los números binarios, sus dígitos se denominan el bit cero (el
primer bit a la derecha), el primer bit (el segundo bit a la derecha) etc. Además,
ya que el sistema binario utiliza solamente dos dígitos (0 y 1), el valor de un bit
puede ser 0 o 1.

No se confunda si se encuentra con un bit que tiene el valor 4, 16 o 64. Son los
valores representados en el sistema decimal. Simplemente, nos hemos
acostumbrado tanto a utilizar los números decimales que estas expresiones
llegaron a ser comunes. Sería correcto decir por ejemplo, “el valor del sexto bit
en cualquier número binario equivale al número decimal 64”. Pero todos somos
humanos y los viejos hábitos mueren difícilmente. Además, ¿cómo le suena
“número uno-uno-cero-uno-cero...”?

BYTE

Un byte consiste en 8 bits colocados uno junto al otro. Si un bit es un dígito, es


lógico que los bytes representen los números. Todas las operaciones
matemáticas se pueden realizar por medio de ellos, como por medio de los
números decimales comunes. Similar a los dígitos de cualquier número, los
dígitos de un byte no tienen el mismo significado. El bit del extremo izquierdo
tiene la mayor ponderación, por eso es denominado el bit más significativo
(MSB). El bit del extremo derecho tiene la menor ponderación, por eso es
denominado el bit menos significativo (LSB). Puesto que los 8 dígitos de un
byte se pueden combinar de 256 maneras diferentes, el mayor número decimal
que se puede representar con un byte es 255 (una combinación representa un
cero).

Un nibble o un cuarteto representa una mitad de byte. Dependiendo de la mitad


del número en cuestión (izquierda o derecha), se les denomina nibbles “altos” o
“bajos”, respectivamente.

Usted seguramente ha pensado alguna vez en cómo es la electrónica dentro


de un circuito integrado digital, un microcontrolador o un microprocesador.
¿Cómo son los circuitos que realizan las operaciones matemáticas
complicadas y toman decisiones? ¿Sabía que sus esquemas, aparentemente
complicadas consisten en sólo unos pocos elementos diferentes, denominados
circuitos lógicos o compuertas lógicas?
1.3 DETALLES IMPORTANTES

El funcionamiento de estos elementos es basado en los principios establecidos


por el matemático británico George Boole en la mitad del siglo 19 - es decir,
¡antes de la invención de la primera bombilla! En breve, la idea principal era de
expresar las formas lógicas por medio de las funciones algebraicas. Tal idea
pronto se transformó en un producto práctico que se convirtió más tarde en lo
que hoy en día conocemos como circuitos lógicos Y (AND), O (OR) o NO
(NOT). El principio de su funcionamiento es conocido como algebra de Boole.
CIRCUITOS LÓGICOS
Algunas instrucciones de programa utilizadas por un microcontrolador
funcionan de la misma manera que las compuertas lógicas, pero en forma de
comandos. A continuación vamos a explicar el principio de su funcionamiento.

COMPUERTA Y (AND)

Una compuerta lógica “Y” dispone de dos o más entradas y de una salida. En
este caso la compuerta utilizada dispone de sólo dos entradas. Un uno lógico
(1) aparecerá en su salida sólo en caso de que ambas entradas (A Y B) sean
llevadas a alto (1). La tabla a la derecha es la tabla de verdad que muestra la
relación entre las entradas y salidas de la compuerta. El principio de
funcionamiento es el mismo cuando la compuerta disponga de más de dos
entradas: la salida proporciona un uno lógico (1) sólo si todas las entradas son
llevadas a alto (1).

Cualquier otra combinación de voltajes de entrada proporcionará un cero lógico


(0) en su salida. Utilizada en el programa, la operación Y lógico es realizada
por una instrucción de programa, de la que vamos a hablar más tarde. Por
ahora basta con conocer que Y lógico en un programa se refiere a la
realización de este tipo de operación sobre los bits correspondientes de dos
registros diferentes.

COMPUERTA O (OR)
De manera similar, la compuerta O también dispone de dos o más entradas y
de una salida. Si la compuerta dispone de sólo dos entradas, es aplicable lo
siguiente: la salida proporciona un uno lógico (1) si una u otra entrada (A o B)
es llevada a alto (1). En caso de que la compuerta O disponga de más de dos
entradas, es aplicable lo siguiente: La salida proporciona un uno lógico (1) si
por lo menos una entrada es llevada a alto (1). Si todas las entradas están a
cero lógico (0), la salida estará a cero lógico (0) también.

En un programa, la operación O lógico se realiza de la misma manera que la


operación Y.

COMPUERTA NO (NOT)

La compuerta lógica NO dispone de una sola entrada y una sola salida, por lo
que funciona muy simplemente. Cuando un cero lógico (0) aparezca en su
entrada, la salida proporciona un uno lógico (1) y viceversa. Esto significa que
esta compuerta invierte las señales por sí mismas y por eso es denominada
inversor.
En el programa la operación lógica NO se realiza sobre un byte. El resultado es
un byte con los bits invertidos. Si los bits de un byte se consideran número, el
valor invertido es un complemento a ese número. El complemento de un
número es el valor que se añade al número hasta llegar al mayor número
binario de 8 dígitos. En otras palabras, la suma de un dígito de 8 números y de
su complemento es siempre 255.

COMPUERTA XOR (O EXCLUSIVA)

La compuerta XOR (O EXCLUSIVA) es un poco complicada en comparación


con las demás. Representa una combinación de todas las compuertas
anteriormente descritas. La salida proporciona un uno lógico (1) sólo si sus
entradas están en estados lógicos diferentes.
En el programa, esta operación se utiliza con frecuencia para comparar dos
bytes. La resta se puede utilizar con el mismo propósito (si el resultado es 0,
los bytes son iguales). A diferencia de la resta, la ventaja de esta operación
lógica es que no es posible obtener los resultados negativos.

REGISTROS

Un registro o una celda de memoria es un circuito electrónico que puede


memorizar el estado de un byte.

REGISTROS SFR

A diferencia de los registros que no tienen ninguna función especial y


predeterminada, cada microcontrolador dispone de un número de registros de
funciones especiales (SFR), con la función predeterminada por el fabricante.
Sus bits están conectados a los circuitos internos del microcontrolador tales
como temporizadores, convertidores A/D, osciladores entre otros, lo que
significa que directamente manejan el funcionamiento de estos circuitos, o sea
del microcontrolador. Imagínese ocho interruptores que manejan el
funcionamiento de un circuito pequeño dentro del microcontrolador. Los
registros SFR hacen exactamente lo mismo.

En otras palabras, el estado de los bits de registros se fija dentro de programa,


los registros SFR dirigen los pequeños circuitos dentro del microcontrolador,
estos circuitos se conectan por los pines del microcontrolador a un dispositivo
periférico utilizado para... Bueno, depende de usted.

PUERTOS DE ENTRADA/SALIDA (E/S)

Para hacer útil un microcontrolador, hay que conectarlo a un dispositivo


externo, o sea, a un periférico. Cada microcontrolador tiene uno o más registros
(denominados puertos) conectados a los pines en el microcontrolador. ¿Por
qué se denominan como puertos de entrada/salida? Porque usted puede
cambiar la función de cada pin como quiera. Por ejemplo, usted desea que su
dispositivo encienda y apague los tres señales LEDs y que simultáneamente
monitoree el estado lógico de 5 sensores o botones de presión. Uno de los
puertos debe estar configurado de tal manera que haya tres salidas
(conectadas a los LEDs) y cinco entradas (conectadas a los sensores). Eso se
realiza simplemente por medio de software, lo que significa que la función de
algún pin puede ser cambiada durante el funcionamiento.
Una de las características más importantes de los pines de entrada/salida (E/S)
es la corriente máxima que pueden entregar/recibir. En la mayoría de los
microcontroladores la corriente obtenida de un pin es suficiente para activar un
LED u otro dispositivo de baja corriente (10-20mA). Mientras más pines de E/S
haya, más baja es la corriente máxima de un pin. En otras palabras, todos los
puertos de E/S comparten la corriente máxima declarada en la hoja de
especificación técnica del microprocesador.

Otra característica importante de los pines es que pueden disponer de los


resistores pull-up. Estos resistores conectan los pines al polo positivo del
voltaje de la fuente de alimentación y su efecto se puede ver al configurar el pin
como una entrada conectada a un interruptor mecánico o a un botón de
presión. Las últimas versiones de los microcontroladores tienen las resistencias
pull-up configurables por software.

Cada puerto de E/S normalmente está bajo el control de un registro SFR


especializado, lo que significa que cada bit de ese registro determina el estado
del pin correspondiente en el el microcontrolador. Por ejemplo, al escribir un
uno lógico (1) a un bit del registro de control (SFR), el pin apropiado del puerto
se configura automáticamente como salida. Eso significa que el voltaje llevado
a ese pin se puede leer como 0 o 1 lógico. En caso contrario, al escribir 0 al
registro SFR, el pin apropiado del puerto se configura como salida. Su voltaje
(0V o 5V) corresponde al estado del bit apropiado del registro del puerto.
UNIDAD DE MEMORIA

La unidad de memoria es una parte del microcontrolador utilizada para


almacenar los datos. La manera más fácil de explicarlo es compararlo con un
armario grande con muchos cajones. Si marcamos los cajones claramente,
será fácil acceder a cualquiera de sus contenidos al leer la etiqueta en la parte
delantera del cajón.

De manera similar, cada dirección de memoria corresponde a una localidad de


memoria. El contenido de cualquier localidad se puede leer y se le puede
acceder al direccionarla. La memoria se puede escribir en la localidad o leer.

Hay varios tipos de memoria dentro del microcontrolador:

MEMORIA ROM (READ ONLY MEMORY) - MEMORIA DE SÓLO LECTURA

La memoria ROM se utiliza para guardar permanentemente el programa que se


está ejecutando. El tamaño de programa que se puede escribir depende del
tamaño de esta memoria. Los microcontroladores actuales normalmente
utilizan el direccionamiento de 16 bits, que significa que son capaces de
direccionar hasta 64 Kb de memoria, o sea 65535 localidades. Por ejemplo, si
usted es principiante, su programa excederá pocas veces el límite de varios
cientos de instrucciones. Hay varios tipos de memoria ROM.

Rom de máscara (enmascarada) - MROM


La ROM enmascarada es un tipo de ROM cuyo contenido es programado por
el fabricante. El término “de máscara” viene del proceso de fabricación, donde
las partes del chip se plasman en las mascaras utilizadas durante el proceso de
fotolitografía. En caso de fabricación de grandes series, el precio es muy bajo.
Olvide la idea de modificarla...

OTP ROM (One Time Programmable ROM) - ROM programable una sola
vez

La memoria programable una sola vez permite descargar un programa en el


chip, pero como dice su nombre, una sola vez. Si se detecta un error después
de descargarlo, lo único que se puede hacer es descargar el programa
correcto en otro chip.

UV EPROM (UV Erasable Programmable ROM) - ROM programable


borrable por rayos ultravioleta

El encapsulado de este microcontrolador tiene una “ventana” reconocible en la


parte alta. Eso permite exponer la superficie del chip de silicio a la luz de
ultravioleta y borrar el programa completamente en varios minutos. Luego es
posible descargar un nuevo programa en él.

La instalación de esta ventana es complicada, lo que por supuesto afecta al


precio. Desde nuestro punto de vista, desgraciadamente, de manera negativa...

Memoria Flash

Este tipo de memoria se inventó en los años 80 en los laboratorios de la


compañía INTEL, como forma desarrollada de la memoria UV EPROM. Ya que
es posible escribir y borrar el contenido de esta memoria prácticamente un
número ilimitado de veces, los microcontroladores con memoria Flash son
perfectos para estudiar, experimentar y para la fabricación en pequeña escala.
Por la gran popularidad de esta memoria, la mayoría de los microconroladores
se fabrican con tecnología flash hoy en día. Si usted va a comprar un
microcontrolador, ¡éste es en definitiva la opción perfecta!
MEMORIA RAM (RANDOM ACCESS MEMORY) - MEMORIA DE ACCESO
ALEATORIO

Al apagar la fuente de alimentación, se pierde el contenido de la memoria RAM.


Se utiliza para almacenar temporalmente los datos y los resultados inmediatos
creados y utilizados durante el funcionamiento del microcontrolador. Por
ejemplo, si el programa ejecuta la adición (de cualquier cosa) es necesario
tener un registro que representa lo que se llama “suma” en vida cotidiana. Con
tal propósito, uno de los registros de la RAM es denominado “suma” y se utiliza
para almacenar los resultados de la adición.

MEMORIA EEPROM (ELECTRICALLY ERASABLE PROGRAMMABLE


ROM) - ROM PROGRAMABLE Y BORRABLE ELÉCTRICAMENTE

El contenido de la EEPROM se puede cambiar durante el funcionamiento


(similar a la RAM), pero se queda permanentemente guardado después de la
pérdida de la fuente de alimentación (similar a la ROM). Por lo tanto, la
EEPROM se utiliza con frecuencia para almacenar los valores creados durante
el funcionamiento, que tienen que estar permanentemente guardados. Por
ejemplo, si usted ha diseñado una llave electrónica o un alarma, sería
estupendo permitir al usuario crear e introducir una contraseña por su cuenta.
Por supuesto, la nueva contraseña tiene que estar guardada al apagar la fuente
de alimentación. En tal caso una solución perfecta es el microcontrolador con
una EEPROM embebida.

INTERRUPCIÓN

La mayoría de programas utilizan interrupciones durante ejecución de


programa regular. El propósito del microcontrolador generalmente consiste en
reaccionar a los cambios en su entorno. En otras palabras, cuando ocurre algo,
el microcontrolador reacciona de alguna manera... Por ejemplo, al apretar el
botón del mando a distancia, el microcontrolador lo registra y responde al
comando cambiando de canal, subiendo o bajando el volumen etc. Si el
microcontrolador pasará la mayoría del tiempo comprobando varios botones sin
parar - las horas, los días, esto no sería nada práctico.

Por lo tanto, el microcontrolador “aprendió un truco” durante su evolución. En


vez de seguir comprobando algún pin o bit, el microconrolador deja su “trabajo
de esperar” a un “experto” que reaccionará sólo en caso de que suceda algo
digno de atención.
La señal que informa al procesador central acerca de tal acontecimiento se
denomina INTERRUPCIÓN.

UNIDAD CENTRAL DE PROCESAMIENTO (CENTRAL PROCESSOR UNIT -


CPU)

Como indica su nombre, esto es una unidad que controla todos los procesos
dentro del microcontrolador. Consiste en varias unidades más pequeñas, de las
que las más importantes son:

 Decodificador de instrucciones es la parte que descodifica las


instrucciones del programa y acciona otros circuitos basándose en esto.
El “conjunto de instrucciones” que es diferente para cada familia de
microcontrolador expresa las capacidades de este circuito;
 Unidad lógica aritmética (Arithmetical Logical Unit - ALU) realiza
todas las operaciones matemáticas y lógicas sobre datos; y
 Acumulador o registro de trabajo. Es un registro SFR estrechamente
relacionado con el funcionamiento de la ALU. Es utilizado para
almacenar todos los datos sobre los que se debe realizar alguna
operación (sumar, mover). También almacena los resultados preparados
para el procesamiento futuro. Uno de los registros SFR, denominado
Registro Status (PSW), está estrechamente relacionado con el
acumulador. Muestra el “estado” de un número almacenado en el
acumulador (el número es mayor o menor que cero etc.) en cualquier
instante dado.

BUS

El bus está formado por 8, 16 o más cables. Hay dos tipos de buses: el bus de
direcciones y el bus de datos. El bus de direcciones consiste en tantas líneas
como sean necesarias para direccionar la memoria. Se utiliza para transmitir la
dirección de la CPU a la memoria. El bus de datos es tan ancho como los
datos, en este caso es de 8 bits o líneas de ancho. Se utiliza para conectar
todos los circuitos dentro del microcontrolador.

COMUNICACIÓN EN SERIE
La conexión paralela entre el microcontrolador y los periféricos a través de los
puertos de entrada/salida es una solución perfecta para las distancias cortas -
hasta varios metros. No obstante, en otros casos cuando es necesario
establecer comunicación entre dos dispositivos a largas distancias no es
posible utilizar la conexión paralela. En vez de eso, se utiliza la conexión en
serie.

Hoy en día, la mayoría de los microcontroladores llevan incorporados varios


sistemas diferentes para la comunicación en serie, como un equipo estándar.
Cuál de estos sistemas se utilizará en un caso concreto, depende de muchos
factores, de los que más importantes son:

 ¿Con cuántos dispositivos el microcontrolador tiene que intercambiar los


datos?
 ¿Cuál es la velocidad del intercambio de datos obligatoria?
 ¿Cuál es la distancia entre los dispositivos?
 ¿Es necesario transmitir y recibir los datos simultáneamente?

Una de las cosas más importantes en cuanto a la comunicación en serie es el


Protocolo que debe ser estrictamente observado. Es un conjunto de reglas que
se aplican obligatoriamente para que los dispositivos puedan interpretar
correctamente los datos que intercambian mutuamente. Afortunadamente, los
microcontroladores se encargan de eso automáticamente, así que el trabajo de
programador/usuario es reducido a la escritura y lectura de datos.

VELOCIDAD DE TRANSMISIÓN SERIAL

La velocidad de transmisión serial (baud rate) es el término utilizado para


denotar el número de bits transmitidos por segundo [bps]. ¡Fíjese que este
término se refiere a bits, y no a bytes! El protocolo normalmente requiere que
cada byte se transmita junto con varios bits de control. Eso quiere decir que un
byte en un flujo de datos serial puede consistir en 11 bits. Por ejemplo, si
velocidad de transmisión serial es 300 bps un máximo de 37 y un mínimo de 27
bytes se pueden transmitir por segundo.
Los sistemas de comunicación serial más utilizados son:

I2C (INTER INTEGRATED CIRCUIT) - CIRCUITO INTER-INTEGRADO


Circuito inter-integrado es un sistema para el intercambio de datos serial entre
los microcontroladores y los circuitos integrados especializados de generación.
Se utiliza cuando la distancia entre ellos es corta (el receptor y el transmisor
están normalmente en la misma placa de circuito impreso). La conexión se
establece por medio de dos líneas - una se utiliza para transmitir los datos,
mientras que la otra se utiliza para la sincronización (la señal de reloj). Como
se muestra en la figura, un dispositivo es siempre el principal (master -
maestro), el que realiza el direccionamiento de un chip subordinado (slave -
esclavo) antes de que se inicie la comunicación. De esta manera un
microcontrolador puede comunicarse con 112 dispositivos diferentes. La
velocidad de transmisión serial es normalmente 100 Kb/seg (el modo estándar)
o 10 Kb/seg (modo de velocidad de transmisión baja). Recientemente han
aparecido los sistemas con la velocidad de transmisión serial 3.4 Mb/sec. La
distancia entre los dispositivos que se comunican por el bus I2C está limitada a
unos metros.
SPI (SERIAL PERIPHERAL INTERFACE BUS) - BUS SERIAL DE INTERFAZ
DE PERIFÉRICOS

Un bus serial de interfaz de periféricos es un sistema para la comunicación


serial que utiliza hasta cuatro líneas (normalmente solo son necesarias tres) -
para recibir los datos, para transmitir los datos, para sincronizar y (opcional)
para seleccionar el dispositivo con el que se comunica. Esto es la conexión full
duplex, lo que significa que los datos se envían y se reciben simultáneamente.

La velocidad de transmisión máxima es mayor que en el sistema de conexión


I2C.

UART (UNIVERSAL ASYNCHRONOUS RECEIVER/TRANSMITTER) -


TRANSMISOR-RECEPTOR ASÍNCRONO UNIVERSAL

Este tipo de conexión es asíncrona, lo que significa que no se utiliza una línea
especial para transmitir la señal de reloj. En algunas aplicaciones este rasgo es
crucial (por ejemplo, en mandar datos a distancia por RF o por luz infrarroja).
Puesto que se utiliza sólo una línea de comunicación, tanto el receptor como el
transmisor reciben y envían los datos a velocidad misma que ha sido
predefinida para mantener la sincronización necesaria. Esto es una manera
simple de transmitir datos puesto que básicamente representa una conversión
de datos de 8 bits de paralelo a serial. La velocidad de transmisión no es alta,
es hasta 1 Mbit/sec.

OSCILADOR
Los pulsos uniformes generados por el oscilador permiten el funcionamiento
armónico y síncrono de todos los circuitos del microcontrolador. El oscilador se
configura normalmente de tal manera que utilice un cristal de cuarzo o
resonador cerámico para estabilización de frecuencia. Además, puede
funcionar como un circuito autónomo (como oscilador RC). Es importante decir
que las instrucciones del programa no se ejecutan a la velocidad impuesta por
el mismo oscilador sino varias veces más despacio. Eso ocurre porque cada
instrucción se ejecuta en varios ciclos del oscilador. En algunos
microcontroladores se necesita el mismo número de ciclos para ejecutar todas
las instrucciones, mientras que en otros el tiempo de ejecución no es el mismo
para todas las instrucciones. Por consiguiente, si el sistema utiliza el cristal de
cuarzo con una frecuencia de 20 MHZ, el tiempo de ejecución de una
instrucción de programa no es 50 nS, sino 200, 400 o 800 nS dependiendo del
tipo del microcontrolador.

CIRCUITO DE ALIMENTACIÓN

Hay que mencionar dos cosas dignas de atención con relación al circuito de la
fuente de alimentación de microcontroladores:

 Brown out es un estado potencialmente peligroso que ocurre al apagar


el microcontrolador o en caso de que el voltaje de la fuente de
alimentación salga de unos márgenes debido al ruido eléctrico. Como el
microcontrolador dispone de varios circuitos que funcionan a niveles de
voltaje diferentes, ese estado puede causar un comportamiento
descontrolado. Para evitarlo, el microcontrolador normalmente tiene un
circuito incorporado para el brown out reset. El circuito reinicia
inmediatamente el microcontrolador si el voltaje de alimentación cae por
debajo del límite.
 El pin de reset (reinicio), marcado frecuentemente con MCLR (Master
Clear Reset), sirve para el reinicio externo del microcontrolador al aplicar
un cero (0) o un uno (1) lógico dependiendo del tipo del
microcontrolador. En caso de que el circuito brown out no esté
incorporado, un simple circuito externo para el brown out reset se puede
conectar al pin MCLR.
TEMPORIZADORES/CONTADORES

El oscilador del microcontrolador utiliza cristal de cuarzo para su


funcionamiento. Aunque no se trata de la solución más simple, hay muchas
razones para utilizarlo. La frecuencia del oscilador es definida con precisión y
muy estable, así que siempre genera los pulsos del mismo ancho, lo que los
hace perfectos para medición de tiempo. Tales osciladores se utilizan en los
relojes de cuarzo. Si es necesario medir el tiempo transcurrido entre dos
eventos, basta con contar los pulsos generados por este oscilador. Esto es
exactamente lo que hace el temporizador.
La mayoría de los programas utiliza estos cronómetros electrónicos en
miniatura. Generalmente son registros SFR de 8 o 16 bits cuyo contenido se
aumenta automáticamente con cada pulso. ¡Una vez que se llena el registro, se
genera una interrupción!

Si el temporizador utiliza el oscilador de cuarzo interno para su funcionamiento,


es posible medir el tiempo entre dos eventos (el valor de registro en el
momento de iniciar la medición es T1, en el momento de finalizar la medición
es T2, el tiempo transcurrido es igual al resultado de la resta T2 - T1). Si los
registros se aumentan con los pulsos que vienen de la fuente externa, tal
temporizador se convierte en un contador.

Esto es una explicación simple de su funcionamiento. Es un poco más


complicado en práctica.

¿CÓMO FUNCIONAN LOS TEMPORIZADORES?

En práctica, los pulsos generados por el oscilador de cuarzo son llevados al


circuito una vez por cada ciclo de máquina directamente o por el pre-escalador,
lo que aumenta el número en el registro del temporizador. Si una instrucción
(un ciclo de máquina) dura cuatro períodos del oscilador de cuarzo, este
número será cambiado un millón de veces por segundo (cada microsegundo) al
incorporar al cuarzo que oscila con una frecuencia de 4 MHz.

Es fácil de medir los intervalos de tiempo cortos de la manera descrita


anteriormente (hasta 256 microsegundos porque es el mayor número que un
registro puede contener). Esta obvia desventaja se puede superar de varias
maneras: al utilizar el oscilador más lento, por medio de registros con más bits,
del pre-escalador o de la interrupción. Las primeras dos soluciones tienen
algunas debilidades así que se recomienda utilizar el pre-escalador y/o la
interrupción.

UTILIZAR UN PREESCALADOR EN EL FUNCIONAMIENTO DEL


TEMPORIZADOR

Un pre-escalador es un dispositivo electrónico utilizado para dividir la


frecuencia por un factor predeterminado. Esto quiere decir que se necesita
llevar 1, 2, 4 o más pulsos a su entrada para generar un pulso a la salida. La
mayoría de los microcontroladores disponen de uno o más pre-escaladores
incorporados y su tasa de división puede ser cambiada dentro del programa. El
pre-escalador se utiliza cuando es necesario medir los períodos de tiempo más
largos. Si el temporizador y el temporizador perro guardián comparten un pre-
escalador, éste no se puede utilizar por los dos simultáneamente.

UTILIZAR UNA INTERRUPCIÓN EN EL FUNCIONAMIENTO DEL


TEMPORIZADOR

Si el registro del temporizador es de 8 bits, el mayor número que se puede


escribir en él es 255 (en los registros de 16 bits es el número 65.535). Si se
excede este número, el temporizador se reinicia automáticamente y el conteo
comienza de nuevo en cero. Esto es denominado desbordamiento o
sobreflujo (overflow). Permitido por el programa, el desbordamiento puede
provocar una interrupción, lo que abre completamente nuevas posibilidades.
Por ejemplo, el estado de registros utilizados para contar segundos, minutos o
días puede ser implementado en una rutina de interrupción. El proceso entero
(excepto la rutina de interrupción) se lleva a cabo internamente, lo que permite
que los circuitos principales del microcontrolador funcionen regularmente.
La figura anterior describe el uso de una interrupción en el funcionamiento del
temporizador. Al asignarle un pre-escalador al temporizador, se producen
retrasos de duración arbitraria con mínima interferencia en la ejecución del
programa principal.

CONTADORES

Si un temporizador se suministra por los pulsos ingresados por el pin de


entrada en el microcontrolador, se produce un contador. Evidentemente, es el
mismo circuito electrónico. La única diferencia es que los pulsos para contar se
ingresan por el pin de entrada y que su duración (anchura) no es definida. Por
eso, no se pueden utilizar para medición de tiempo, sino que se utilizan para
otros propósitos, por ejemplo: contar los productos en la cadena de montaje,
número de rotaciones del eje de un motor, pasajeros etc. (dependiendo del
sensor utilizado.

TEMPORIZADOR PERRO GUARDIÁN (WATCHDOG)

El perro guardián es un temporizador conectado a un oscilador RC


completamente independiente dentro del microcontrolador.

Si el perro guardián está habilitado, cada vez que cuenta hasta el máximo valor
en el que ocurre el desbordamiento del registro se genera una señal de reinicio
del microcontrolador y la ejecución de programa inicia en la primera instrucción.
El punto es evitar que eso ocurra al utilizar el comando adecuado.

La idea se basa en el hecho de que cada programa se ejecuta en varios


bucles, más largos o cortos. Si las instrucciones que reinician el temporizador
perro guardián se colocan en lugares estratégicos del programa, aparte los
comandos que se ejecutan regularmente, el funcionamiento del perro guardián
no afectará a la ejecución del programa. Si por cualquier razón (ruidos
eléctricos frecuentes en la industria) el contador de programa “se queda
atrapado” dentro de un bucle infinito, el valor del registro continuará aumentado
por el temporizador perro guardián alcanzará el máximo valor, el registro se
desbordará y, ¡aleluya! ¡Ocurre el reinicio!

CONVERTIDOR A/D
Las señales del mundo real son muy diferentes de las que “entiende” el
microcontrolador (ceros y unos), así que deben ser convertidas para que el
microcontrolador pueda entenderlas. Un convertidor analógico-digital es un
circuito electrónico encargado de convertir las señales continuas en números
digitales discretos. En otras palabras, este circuito convierte un número real en
un número binario y se lo envía a la CPU para ser procesado. Este módulo se
utiliza para medir el voltaje en el pin de entrada.

El resultado de esta medición es un número (el valor digital) utilizado y


procesado más tarde en el programa.
ARQUITECTURA INTERNA

Todos los microcontroladores actuales utilizan uno de dos modelos básicos de


arquitectura denominados Harvard y von-Neumann.
Son dos maneras diferentes del intercambio de datos entre la CPU y la
memoria.

Arquitectura de von-Neumann

Los microcontroladores que utilizan la arquitectura von- Neumann disponen de


un solo bloque de memoria y de un bus de datos de 8 bits. Como todos los
datos se intercambian por medio de estas 8 líneas, este bus está
sobrecargado, y la comunicación por si misma es muy lenta e ineficaz. La CPU
puede leer una instrucción o leer/escribir datos de/en la memoria. Los dos
procesos no pueden ocurrir a la vez puesto que las instrucciones y los datos
utilizan el mismo bus. Por ejemplo, si alguna línea de programa dice que el
registro de la memoria RAM llamado “SUM” debe ser aumentado por uno
(instrucción: incf SUMA), el microcontrolador hará lo siguiente:
1. Leer la parte de la instrucción de programa que especifica QUÉ es lo que
debe realizar (en este caso es la instrucción para incrementar “incf”)
2. Seguir leyendo la misma instrucción que especifica sobre CUÁL dato lo
debe realizar (en este caso es el contenido del registro “SUMA”)
3. Después de haber sido incrementado, el contenido de este registro se
debe escribir en el registro del que fue leído (dirección del registro
“SUMA”)
El mismo bus de datos se utiliza para todas estas operaciones intermedias.

ARQUITECTURA DE HARVARD

Los microcontroladores que utilizan esta arquitectura disponen de dos buses de


datos diferentes. Uno es de 8 bits de ancho y conecta la CPU con la memoria
RAM. El otro consiste en varias líneas (12, 14 o 16) y conecta a la CPU y la
memoria ROM. Por consiguiente, la CPU puede leer las instrucciones y realizar
el acceso a la memoria de datos a la vez. Puesto que todos los registros de la
memoria RAM son de 8 bits de ancho, todos los datos dentro del
microcontrolador que se intercambian son de la misma anchura. Durante el
proceso de la escritura de programa, sólo se manejan los datos de 8 bits. En
otras palabras, todo lo que usted podrá cambiar en el programa y a lo que
podrá afectar será de 8 bits de ancho. Todos los programas escritos para estos
microcontroladores serán almacenados en la memoria ROM interna del
microcontrolador después de haber sido compilados a código máquina. No
obstante, estas localidades de memoria ROM no tienen 8, sino 12, 14 o 16 bits.
4, 6 o 8 bits adicionales representan una instrucción que especifica a la CPU
qué hacer con los datos de 8 bits.

Las ventajas de este diseño son las siguientes:

Todos los datos en el programa son de un byte (8 bits) de ancho. Como


un bus de datos utilizado para lectura de programa tiene unas líneas
más (12, 14 o 16), tanto la instrucción como el dato se pueden leer
simultáneamente al utilizar estos bits adicionales. Por eso, todas las
instrucciones se ejecutan en un ciclo salvo las instrucciones de salto que
son de dos ciclos.
 El hecho de que un programa (la ROM) y los datos temporales (la RAM)
estén separados, permite a la CPU poder ejecutar dos instrucciones
simultáneamente. Dicho de manera sencilla, mientras que se realiza la
lectura o escritura de la RAM (que marca el fin de una instrucción), la
siguiente instrucción se lee por medio de otro bus.
 En los microcontroladores que utilizan la arquitectura de von-Neumann,
nunca se sabe cuánta memoria ocupará algún programa. Generalmente,
la mayoría de las instrucciones de programa ocupan dos localidades de
memoria (una contiene información sobre QUÉ se debe realizar,
mientras que la otra contiene informa ción sobre CUÁL dato se debe
realizar). Sin embargo, esto no es una fórmula rígida, sino el caso más
frecuente. En los microcontroladores que utilizan una arquitectura
Harvard, el bus de la palabra de programa es más ancho que un byte, lo
que permite que cada palabra de programa esté compuesto por una
instrucción y un dato. En otras palabras, una localidad de memoria - una
instrucción de programa.
JUEGO DE INSTRUCCIONES

El nombre colectivo de todas las instrucciones que puede entender el


microcontrolador es llamado Juego de Instrucciones. Cuando se escribe un
programa en ensamblador, en realidad se especifican instrucciones en el orden
en el que deben ser ejecutadas. La restricción principal es el número de
instrucciones disponibles. Los fabricantes aceptan cualquiera de los dos
enfoques descritos a continuación:
RISC (Reduced Instruction Set Computer) - Computadora con Juego de
Instrucciones Reducidas

En este caso la idea es que el microcontrolador reconoce y ejecuta sólo


operaciones básicas (sumar, restar, copiar etc...) Las operaciones más
complicadas se realizan al combinar éstas (por ejemplo, multiplicación se lleva
a cabo al realizar adición sucesiva). Es como intentar explicarle a alguien con
pocas palabras cómo llegar al aeropuerto en una nueva ciudad. Sin embargo,
no todo es tan oscuro. Además, el microcontrolador es muy rápido así que no
es posible ver todas las “acrobacias” aritméticas que realiza. El usuario sólo
puede ver el resultado final de todas las operaciones. Por último, no es tan
difícil explicar dónde está el aeropuerto si se utilizan las palabras adecuadas
tales como: a la derecha, a la izquierda, el kilómetro etc.

CISC (Complex Instruction Set Computer) - Computadoras con un juego


de instrucciones complejo

¡CISC es opuesto a RISC! Los microcontroladores diseñados para reconocer


más de 200 instrucciones diferentes realmente pueden realizar muchas cosas a
alta velocidad. No obstante, uno debe saber cómo utilizar todas las
posibilidades que ofrece un lenguaje tan rico, lo que no es siempre tan fácil...

¿CÓMO ELEGIR UN MICROCONTROLADOR?

Bueno, si usted es principiante, y ha tomado decisión de trabajar con los


microcontroladores. ¡Felicitaciones por la elección! No obstante, a primera
vista, no es fácil la elección del microcontrolador más adecuado como parece a
la primera vista. ¡El problema no es el pequeño rango de dispositivos a elegir,
sino todo lo contrario!

Antes de empezar a diseñar un dispositivo basado en un microcontrolador,


tome en cuenta lo siguiente: cuántas entradas/líneas son necesarias para su
funcionamiento, realizaría el dispositivo otras operaciones además
encender/apagar un relé, necesita algún modulo especializado tal como el de
comunicación en serie, convertidor A/D etc. Cuando usted tiene una clara
imagen de lo que quiere, el rango de selección se reduce considerablemente, y
le queda pensar en el precio. ¿Va a tener varios dispositivos? ¿Varios cientos?
¿Un millón? De todos modos ahora es más claro.
Si está pensando en todas estas cosas por primera vez, todo le parecerá un
poco confuso. Por esa razón, vaya paso a paso. Antes que nada, seleccione al
fabricante, es decir, la familia de microcontroladores que ofrece. Luego,
aprenda a trabajar con un modelo particular. Sólo aprenda lo que necesite
aprender, no entre demasiado en detalles. Resuelva el problema específico y le
pasará una cosa increíble - será capaz de manejar cualquier modelo del mismo
fabricante...

Más o menos, todo se parece a montar en bicicleta: después de varias caídas


inevitables en el principio, será capaz de mantener el equilibrio y montar en
cualquier otra bicicleta. ¡Por supuesto, nunca se olvida tanto de montar en
bicicleta, como de la destreza de programación!

1.4 MICROCONTROLADORES PIC

Los microcontroladores PIC desarrollados por Microchip Technology son


probablemente la mejor opción si es principiante. Hay varias razones por lo que
esto es verdadero...
El nombre verdadero de este microcontrolador es PICmicro (Peripheral
Interface Controller), conocido bajo el nombre PIC. Su primer antecesor fue
creado en 1975 por la compañía General Instruments. Este chip denominado
PIC1650 fue diseñado para propósitos completamente diferentes. Diez años
más tarde, al añadir una memoria EEPROM, este circuito se convirtió en un
verdadero microcontrolador PIC. Hace unos pocos años la compañía Microchip
Technology fabricó la 5 billonésima muestra. Si está interesado en aprender
más sobre eso, siga leyendo.
If you are interested in learning more about it, just keep on reading.

La idea principal de este libro es proporcionar la información necesaria al


usuario para que sea capaz de utilizar los microcontroladores en la práctica
después de leerlo. Para evitar explicaciones pesadas y las historias infinitas
sobre las características útiles de los microcontroladores diferentes, este libro
describe el funcionamiento de un modelo particular que pertenece a la “clase
media alta”. Es PIC16F887 - bastante poderoso para ser digno de atención y
bastante simple para poder ser utilizado por cualquiera. Así, los siguientes
capítulos describen este microcontrolador en detalle y también se refieren a la
familia PIC entera.
Frecuencia Resolución
ROM RAM Entradas
Familia Pines de reloj. del Compara
[Kbytes] [bytes] A/D
[MHz] convertidor
A/D
Arquitectura de la gama baja de 8 bits, palabra de instrucción de 12 bits
0.375 -
PIC10FXXX 16 - 24 6-8 4-8 0-2 8 0-1
0.75
0.75 -
PIC12FXXX 25 - 38 8 4-8 0-3 8 0-1
1.5
25 - 14 –
PIC16FXXX 0.75 - 3 20 0-3 8 0-2
134 44
18 –
PIC16HVXXX 1.5 25 20 - - -
20
Arquitectura de la gama media de 8 bits, palabra de instrucción de 14 bits
1.75 - 64 -
PIC12FXXX 8 20 0-4 10 1
3.5 128
PIC12HVXXX 1.75 64 8 20 0-4 10 1
64 - 14 –
PIC16FXXX 1.75 - 14 20 0 - 13 8 or 10 0-2
368 64
1.75 - 64 - 14 –
PIC16HVXXX 20 0 - 12 10 2
3.5 128 20
Arquitectura de la gama alta de 8 bits, palabra de instrucción de 16 bits

256 - 18 –
PIC18FXXX 4 - 128 32 - 48 4 - 16 10 or 12 0-3
3936 80

1024 - 28 -
PIC18FXXJXX 8 - 128 40 - 48 10 - 16 10 2
3936 100

768 - 28 –
PIC18FXXKXX 8 - 64 64 10 - 13 10 2
3936 44
Todos los microcontroladores PIC utilizan una arquitectura Harvard, lo que
quiere decir que su memoria de programa está conectada a la CPU por más de
8 líneas. Hay microcontroladores de 12, 14 y 16 bits, dependiendo de la
anchura del bus. La tabla anterior muestra las características principales de
estas tres categorías.

Como se puede ver en la tabla de la página anterior, salvo “los monstruos de


16 bits” PIC 24FXXX y PIC 24HXXX - todos los microcontroladores tienen la
arquitectura Harvard de 8 bits y pertenecen a una de las tres grandes grupos.
Por eso, dependiendo del tamaño de palabra de programa existen la primera,
la segunda y la tercera categoría de microcontroladores, es decir
microcontroladores de 12, 14 o 16 bits. Puesto que disponen del núcleo similar
de 8 bits, todos utilizan el mismo juego de instrucciones y el “esqueleto” básico
de hardware conectado a más o menos unidades periféricas.

Los microcontroladores PIC con palabras de programa de 14 bits parecen ser


la mejor opción para los principiantes. Aquí está el porqué...

JUEGO DE INSTRUCCIONES

El juego de instrucciones para los microcontroladores 16F8XX incluye 35


instrucciones en total. La razón para un número tan reducido de instrucciones
yace en la arquietectura RISC. Esto quiere decir que las instrucciones son bien
optimizadas desde el aspecto de la velocidad operativa, la sencillez de la
arquitectura y la compacidad del código. Lo malo de la arquitectura RISC es
que se espera del programador que haga frente a estas instrucciones. Por
supuesto, esto es relevante sólo si se utiliza el lenguaje ensamblador para la
programación. Este libro se refiere a la programación en el lenguaje de alto
nivel C, lo que significa que la mayor parte del trabajo ya fue hecho por alguien
más. Así, sólo se tienen que utilizar instrucciones relativamente simples.

TIEMPO DE EJECUCIÓN DE INSTRUCCIONES

Todas las instrucciones se ejecutan en un ciclo. La únicas excepciones pueden


ser las instrucciones de ramificación condicional o las instrucciones que
cambian el contenido del contador de programa. En ambos casos, dos ciclos
de reloj son necesarios para la ejecución de la instrucción, mientras que el
segundo ciclo se ejecuta como un NOP (No operation). Las instrucciones de un
ciclo consisten en cuatro ciclos de reloj. Si se utiliza un oscilador de 4 MHz, el
tiempo nominal para la ejecución de la instrucción es 1μS. En cuanto a las
instrucciones de ramificación, el tiempo de ejecución de la instrucción es 2μS.
Juego de instrucciones de los microcontroladores PIC de 14 bits:

INSTRUCCIÓN DESCRIPCIÓN O P E R AC I Ó N B AN D E R A CLK *


Instrucciones para la transmisión de datos
MOVLW k Mover literal a W k -> w 1
Mover el contenido
MOVWF f W -> f 1
de W a f
MOVF f,d Mover el contenido f -> d Z 1 1,
de f a d 2
Borrar el contenido
CLRW 0 -> W Z 1
de W
Borrar el contenido
CLRF f 0 -> f Z 1 2
de f
Intercambiar de f(7:4),(3:0) -> 1,
SWAPF f,d 1
nibbles en f f(3:0),(7:4) 2
Instrucciones aritmético – lógicas
ADDLW k Sumar literal a W W+k -> W C, DC, Z 1
Sumar el contenido 1,
ADDWF f,d W+f -> d C, DC ,Z 1
de W y f 2
SUBLW k Restar W de literal k-W -> W C, DC, Z 1
1,
SUBWF f,d Restar W de f f-W -> d C, DC, Z 1
2
ANDLW k AND W con literal W AND k -> W Z 1
1,
ANDWF f,d AND W con f W AND f -> d Z 1
2
OR inclusivo de W
IORLW k W OR k -> W Z 1
con literal
OR inclusivo de W 1,
IORWF f,d W OR f -> d Z 1
con f 2
OR exclusivo de W 1,
XORWF f,d W XOR k -> W Z 1
con literal 2
OR exclusivo de W
XORLW k W XOR f -> d Z 1
con f
1,
INCF f,d Sumar 1 a f f+1 -> f Z 1
2
1,
DECF f,d Restar 1 a f f-1 -> f Z 1
2
Rotar F a la
1,
RLF f,d izquierda a través C 1
2
del bit de Acarreo
Rotar F a la derecha
1,
RRF f,d a través del bit de C 1
2
Acarreo
1,
COMF f,d Complementar f f -> d Z 1
2
Instrucciones orientadas a bit
Poner a 0 el bit b del 1,
BCF f,b 0 -> f(b) 1
registro f 2
Poner a 1 el bit b del 1,
BSF f,b 1 -> f(b) 1
registro f 2
Instrucciones de control de programa
Saltar si bit b de
BTFSC f,b Skip if f(b) = 0 1 (2) 3
registro f es 0
Saltar si bit b de reg.
BTFSS f,b Skip if f(b) = 1 1 (2) 3
f es 1
Disminuir f en 1. 1,
DECFSZ f,d Saltar si el resultado f-1 -> d skip if Z = 1 1 (2) 2,
es 0 3
Incrementar f en 1. 1,
f+1 -> d skip if Z =
INCFSZ f,d Saltar si el resultado 1 (2) 2,
0
es 1 3
Saltar a una
GOTO k k -> PC 2
dirección
Llamar a una PC -> TOS, k ->
CALL k 2
subrutina PC
Retornar de una
RETURN TOS -> PC 2
subrutina
Retornar con literal
RETLW k k -> W, TOS -> PC 2
en W
Retornar de una TOS -> PC, 1 ->
RETFIE 2
interrupción GIE
Otras instrucciones
TOS -> PC, 1 ->
NOP No operación 1
GIE
Reiniciar el
0 -> WDT, 1 -> TO,
CLRWDT temporizador perro TO, PD 1
1 -> PD
guardián
Poner en estado de 0 -> WDT, 1 -> TO,
SLEEP TO, PD 1
reposo 0 -> PD
*1 Si un registro de E/S está modificado, el valor utilizado será el valor
presentado en los pines del microcontrolador.
*2 Si la instrucción se ejecuta en el registro TMR y si d=1, el pre-escalador será
borrado.
*3 Si la instrucción se ejecuta en el registro TMR y si d=1, el pre-escalador será
borrado.
Arquitectura de los microcontroladores PIC de 8 bits. Cuáles de estos módulos
pertenecerán al microcontrolador, dependerá del tipo de microcontrolador.

Capitulo 2 - Programación de los microcontroladores

Usted seguramente sabe que no es suficiente sólo conectar el microcontrolador


a los otros componentes y encender una fuente de alimentación para hacerlo
funcionar, ¿verdad? Hay que hacer algo más. Se necesita programar el
microcontrolador. Si cree que esto es complicado, está equivocado. Todo el
procedimiento es muy simple. Basta con leer el texto para entender de lo que
estamos hablando.
 2.1 LENGUAJES DE PROGRAMACIÓN
 2.2 CARACTERÍSTICAS PRINCIPALES DEL MIKROC
 2.3 TIPOS DE DATOS EN MIKROC
 2.4 VARIABLES Y CONSTANTES
 2.5 OPERADORES
 2.6 ESTRUCTURAS DE CONTROL
 2.7 TIPOS DE DATOS AVANZADOS
 2.8 FUNCIONES
 2.9 CARACTERÍSTICAS PRINCIPALES DEL PREPROCESADOR
 2.10 MIKROC PRO FOR PIC
 2.11 PROGRAMAR LOS PIC UTILIZANDO MIKROC PRO FOR PIC
2.1 LENGUAJES DE PROGRAMACIÓN

El microcontrolador ejecuta el programa cargado en la memoria Flash. Esto se


denomina el código ejecutable y está compuesto por una serie de ceros y unos,
aparentemente sin significado. Dependiendo de la arquitectura del
microcontrolador, el código binario está compuesto por palabras de 12, 14 o 16
bits de anchura. Cada palabra se interpreta por la CPU como una instrucción a
ser ejecutada durante el funcionamiento del microcontrolador. Todas las
instrucciones que el microcontrolador puede reconocer y ejecutar se les
denominan colectivamente Conjunto de instrucciones. Como es más fácil
trabajar con el sistema de numeración hexadecimal, el código ejecutable se
representa con frecuencia como una serie de los números hexadecimales
denominada código Hex. En los microcontroladores PIC con las palabras de
programa de 14 bits de anchura, el conjunto de instrucciones tiene 35
instrucciones diferentes.

LENGUAJE ENSAMBLADOR

Como el proceso de escribir un código ejecutable era considerablemente


arduo, en consecuencia fue creado el primer lenguaje de programación
denominado ensamblador (ASM). Siguiendo la sintaxis básica del
ensamblador, era más fácil escribir y comprender el código. Las instrucciones
en ensamblador consisten en las abreviaturas con significado y a cada
instrucción corresponde una localidad de memoria. Un programa denominado
ensamblador compila (traduce) las instrucciones del lenguaje ensamblador a
código máquina (código binario).

HEste programa compila instrucción a instrucción sin optimización. Como


permite controlar en detalle todos los procesos puestos en marcha dentro del
chip, este lenguaje de programación todavía sigue siendo popular.

Ventajas de lenguajes de programación de alto nivel

A pesar de todos los lados buenos, el lenguaje ensamblador tiene algunas


desventajas:

 Incluso una sola operación en el programa escrito en ensamblador


consiste en muchas instrucciones, haciéndolo muy largo y difícil de
manejar.
 Cada tipo de microcontrolador tiene su propio conjunto de instrucciones
que un programador tiene que conocer para escribir un programa
 Un programador tiene que conocer el hardware del microcontrolador
para escribir un programa
Programa escrito en C (El mismo programa compilado al código ensamblador):

Los lenguajes de programación de alto nivel (Basic, Pascal, C etc.) fueron


creados con el propósito de superar las desventajas del ensamblador. En
lenguajes de programación de alto nivel varias instrucciones en ensamblador
se sustituyen por una sentencia. El programador ya no tiene que conocer el
conjunto de instrucciones o características del hardware del microcontrolador
utilizado. Ya no es posible conocer exactamente cómo se ejecuta cada
sentencia, de todas formas ya no importa. Aunque siempre se puede insertar
en el programa una secuencia escrita en ensamblador.

Si alguna vez ha escrito un programa para un microcontrolador PIC en lenguaje


ensamblador, probablemente sepa que la arquitectura RISC carece de algunas
instrucciones. Por ejemplo, no hay instrucción apropiada para multiplicar dos
números. Por supuesto, para cada problema hay una solución y éste no es una
excepción gracias a la aritmética que permite realizar las operaciones
complejas al descomponerlas en un gran número operaciones más simples. En
este caso, la multiplicación se puede sustituir con facilidad por adición sucesiva
(a x b = a + a + a + ... + a). Ya estamos en el comienzo de una historia muy
larga... No hay que preocuparse al utilizar uno de estos lenguajes de
programación de alto nivel como es C, porque el compilador encontrará
automáticamente la solución a éste problema y otros similares. Para multiplicar
los números a y b, basta con escribir a*b.

Lenguaje C

El lenguaje C dispone de todas las ventajas de un lenguaje de programación de


alto nivel (anteriormente descritas) y le permite realizar algunas operaciones
tanto sobre los bytes como sobre los bits (operaciones lógicas, desplazamiento
etc.). Las características de C pueden ser muy útiles al programar los
microcontroladores. Además, C está estandarizado (el estándar ANSI), es muy
portable, así que el mismo código se puede utilizar muchas veces en diferentes
proyectos. Lo que lo hace accesible para cualquiera que conozca este lenguaje
sin reparar en el propósito de uso del microcontrolador. C es un lenguaje
compilado, lo que significa que los archivos fuentes que contienen el código C
se traducen a lenguaje máquina por el compilador. Todas estas características
hicieron al C uno de los lenguajes de programación más populares.

La figura anterior es un ejemplo general de lo que sucede durante la


compilación de programa de un lenguaje de programación de alto nivel a bajo
nivel.
2.2 CARACTERÍSTICAS PRINCIPALES DEL MIKROC

A continuación vamos a presentar a los elementos principales del lenguaje


mikroC desarrollado por Mikroelektronika. Este lenguaje es muy similar al C
estándar, no obstante en determinados aspectos difiere del ANSI estándar en
algunas características. Algunas de estas diferencias se refieren a las mejoras,
destinadas a facilitar la programación de los microcontroladores PIC, mientras
que las demás son la consecuencia de la limitación de la arquitectura del
hardware de los PIC. Aquí vamos a presentar características específicas del
lenguaje mikroC en la programación de los microcontroladores PIC. El término
C se utilizará para referirse a las características comunes de los lenguajes C y
mikroC.

Este libro describe una aplicación muy concreta del lenguaje de programación
C utilizado en el compilador mikroC PRO for PIC. En este caso, el compilador
se utiliza para la programación de los microcontroladores PIC.

FASES DE COMPILACIÓN

El proceso de compilación consiste en varios pasos y se ejecuta


automáticamente por el compilador. Por con, un conocimiento básico del
funcionamiento puede ser útil para entender el concepto del lenguaje mikroC.

El archivo fuente contiene el código en mikroC que usted escribe para


programar el microcontrolador. El preprocesador se utiliza automáticamente por
el compilador al iniciarse el proceso de la compilación. El compilador busca las
directivas del preprocesador (que siempre empiezan por ‘#’) dentro del código y
modifica el código fuente de acuerdo con las directivas. En esta fase se llevan
a cabo inclusión de archivos, definición de constantes y macros etc, lo que
facilita el proceso. Más tarde vamos a describir estas directivas en detalle.
El analizador sintáctico (parser) elimina toda la información inútil del código
(comentarios, espacios en blanco). Luego, elcompilador traduce el código a un
archivo binario denominado archivo .mcl. El enlazador (linker) recupera toda la
información requerida para ejecutar el programa de los archivos externos y la
agrupa en un solo archivo (.dbg). Además, un proyecto puede contener más de
un archivo fuente y el programador puede utilizar funciones predefinidas y
agrupadas dentro de los archivos denominados librerías. Por último,
el generador .hex produce un archivo .hex. Es el archivo que se va a cargar en
el microcontrolador.

El proceso entero de la compilación que incluye todos los pasos anteriormente


descritos se le denomina “building”.
ESTRUCTURA DE PROGRAMA

La idea principal de escribir un programa en C es de “romper” un problema


mayor en varios trozos más pequeños. Supongamos que es necesario escribir
un programa para el microcontrolador para medir la temperatura y visualizar los
resultados en un LCD. El proceso de medición se realiza por un sensor que
convierte temperatura en voltaje. El microcontrolador utiliza el convertidor A/D
para convertir este voltaje (valor analógico) en un número (valor digital) que
luego se envía al LCD por medio de varios conductores. En consecuencia, el
programa se divide en cuatro partes, de las que cada una corresponde a una
acción específica:

1. Activar y configurar el convertidor A/D incorporado;


2. Medir el valor analógico;
3. Calcular temperatura; y
4. Enviar los datos en el formato apropiado al LCD;
Los lenguajes de programación de alto nivel como es C le permiten solucionar
este problema con facilidad al escribir cuatro funciones que se ejecutarán
cíclicamente sin parar.

La idea general es de dividir el problema en varios trozos, de los que cada uno
se puede escribir como una sola función. Todos los programas escritos en
mikroC contienen por lo menos una función llamada main() que encierra entre
llaves {} las sentencias a ser ejecutadas. Esto es la primera función a ser
ejecutada al iniciarse la ejecución de programa. Las otras funciones se pueden
llamar dentro de la función main. En otras palabras, podemos decir que la
función main() es obligatoria, mientras que las demás son opcionales. Si
todavía no ha escrito un programa en C, es probable que todo le resulte
confuso. No se preocupe, acéptelo tal como es por el momento y más tarde
entenderá la sintaxis.

¡Y ahora, su primer programa ‘real’! La figura muestra la estructura de


programa, señalando las partes en las que consiste.
La manera de escribir el código en C es muy importante. Por ejemplo, C difiere
entre minúsculas y mayúsculas, así que la función main() no se puede escribir
MAIN() o Main(). Además, note que dos líneas del código dentro de la función
terminan con un punto y coma. En C todas las sentencias deben terminar con
un punto y coma ‘;’, así el compilador puede aislarlas y traducirlas a código
máquina.
COMENTARIOS

Los comentarios son las partes del programa utilizados para aclarar las
instrucciones de programa o para proporcionar más información al respecto. El
compilador no hace caso a los comentarios y no los compila al código
ejecutable. Dicho de manera sencilla, el compilador es capaz de reconocer los
caracteres especiales utilizados para designar dónde los comentarios
comienzan y terminan y no hace nada de caso al texto entre ellos durante la
compilación. Hay dos tipos de tales caracteres. Unos designan los comentarios
largos que ocupan varias líneas de programa marcados por la secuencia
especial /*...*/, mientras que otros designan los comentarios cortos que caben
en una sola línea //. Aunque los comentarios no pueden afectar a la ejecución
de programa, son tan importantes como cualquier otra parte de programa. Aquí
está el porqué... Con frecuencia es necesario mejorar, modificar, actualizar,
simplificar un programa... No es posible interpretar incluso los programas
simples sin utilizar los comentarios.

2.3 TIPOS DE DATOS EN MIKROC

En el lenguaje C, los datos tienen un tipo, o sea, cada dato utilizado en el


programa debe tener su tipo especificado. Esto permite al compilador conocer
el tamaño de dato (número de bytes requerido en la memoria) y su
representación. Hay varios tipos de datos que se pueden utilizar en el lenguaje
de programación mikroC dependiendo del tamaño de dato y del rango de
valores. La tabla muestra el rango de valores que los datos pueden tener
cuando se utilizan en su forma básica.

T AM A Ñ O
TIPO DE
DESCRIPCIÓN (NÚMERO DE R AN G O D E V AL O R E S
D AT O
BITS)
char Texto (caracteres) 8 de 0 a 255
int Valores enteros 16 de -32768 a 32767
de ±1.17549435082·10-38 a
float Valores en punto flotante 32
±6.80564774407·1038
Valores en punto flotante de de ±1.17549435082·10-38 a
double 32
doble precisión ±6.80564774407·1038
*Debido a las limitaciones impuestas por el hardware del microcontrolador, es
imposible alcanzar una mayor precisión de datos que la del tipo float. Por eso,
el tipo double en mikroC equivale al tipo float.

Al añadir un prefijo (calificador) a cualquier tipo de dato entero o carácter, el


rango de sus posibles valores cambia así como el número de los bytes de
memoria necesarios. Por defecto, los datos de tipo int son con signo, mientras
que los de tipo char son sin signo. El calificador signed (con signo) indica que el
dato puede ser positivo o negativo. El prefijo unsigned indica que el dato puede
ser sólo positivo. Note que el prefijo es opcional.
T I P O D E D AT O
T I P O D E D AT O T AM A Ñ O ( N Ú M E R O D E B I T S )
CON PREFIJO
char signed char 8
unsigned int 16
short int 8
int signed short int 8
long int 32
signed long int 32
Tipo entero (int)

Un entero es un número sin parte fraccionaria que puede estar expresado en


los siguientes formatos:

 Hexadecimal (base 16): el número empieza con 0x (o 0X). Los enteros


hexadecimales consisten en los dígitos (de 0 a 9) y/o las letras (A, B,
C,D, E, F). Por ejemplo: ‘0x1A’.
 Decimal (base 10): el número consiste en los dígitos (de 0 a 9). El primer
dígito no puede ser 0. En este formato, se puede introducir el signo de
número (‘+’ o ‘-’). Por ejemplo: 569, -25, +1500.
 Octal (base 8): los números se representan a base 8 utilizando sólo 8
dígitos (de 0 a 7). Los enteros octales empiezan con 0. Por ejemplo:
‘056’.
 Binario: cuando un entero empieza con 0b (o 0B) se representan como
una serie de bits (‘0’ y ‘1’). Por ejemplo: 0B10011111

0x11 // formato hexadecimal equivale a decimal 17


11 // formato decimal
-152 // formato decimal
011 // formato octal equivale a decimal 9
0b11 // formato binario equivale a decimal 3

Tipo punto flotante (float)

El tipo punto flotante (float) se utiliza para los números reales con el punto
decimal. Los datos de tipo float se pueden representar de varias maneras. Un
dato float es siempre consigno (signed).

0. // = 0.0
-1.23 // = -1.23
23.45e6 // = 23.45 * 10^6
2e-5 // = 2.0 * 10^-5
3E+10 // = 3.0 * 10^10
.09E34 // = 0.09 * 10^34

Tipo carácter (char)


El tipo char es considerado como un entero por el compilador. No obstante, se
utiliza normalmente para los datos de tipo carácter. Un dato de tipo carácter
está encerrado entre comillas y codificado en un carácter ASCII.

59 // entero
'p' // carácter ASCII 'p'

Una secuencia de caracteres es denominada cadena (string). Las cadenas


están encerradas entre comillas dobles, por ejemplo:

"Presione el botón RA0"

2.4 VARIABLES Y CONSTANTES


Definiciones

Una variable es un objeto nombrado capaz de contener un dato que puede ser
modificado durante la ejecución de programa. En C, las variables tienen tipo,
que significa que es necesario especificar el tipo de dato que se le asigna a una
variable (int, float etc.). Las variables se almacenan en la memoria RAM y el
espacio de memoria que ocupan (en bytes) depende de su tipo.

/* dos líneas de programa consecutivas. En la primera línea del programa


se define el tipo de variable */

int a = 1000; // Variable a es de tipo int y equivale a 1000


a = 15; // a equivale a 15

Una constante tiene las mismas características que una variable excepto el
hecho de que su valor asignado no puede ser cambiado durante la ejecución
de programa. A diferencia de las variables, las constantes se almacenan en la
memoria Flash del microcontrolador para guardar el mayor espacio posible de
memoria RAM. El compilador las reconoce por el nombre y el prefijo const. En
mikroC, el compilador reconoce automáticamente el tipo de dato de una
constante, así que no es necesario especificar el tipo adicionalmente.

/* dos líneas de programa consecutivas */

const A = 1000 // el valor de la constante A está definido


A = 15; // ¡ERROR! no se puede modificar el valor de la constante
Cada variable o constante debe tener un identificador que lo distingue de otras
variables y constantes. Refiérase a los ejemplos anteriores, a y A son
identificadores.

Reglas para nombrar

En mikroC, los identificadores pueden ser tan largos como quiera. Sin
embargo, hay varias restricciones:

 Los identificadores pueden incluir cualquiera de los caracteres


alfabéticos A-Z (a-z), los dígitos 0-9 y el carácter subrayado '_'. El
compilador es sensible a la diferencia entre minúsculas y mayúsculas.
Los nombres de funciones y variables se escriben con frecuencia con
minúsculas, mientras que los nombres de constantes se escriben con
mayúsculas.
 Los identificadores no pueden empezar con un dígito.
 Los identificadores no pueden coincidir con las palabras clave del
lenguaje mikroC, porque son las palabras reservadas del compilador.
El compilador mikroC reconoce 33 palabras clave:

M I K R O C - P AL A B R AS C L AV E
absolute data if return typedef
asm default inline rx typeid
at delete int sfr typename
auto do io short union
bit double long signed unsigned
bool else mutable sizeof using
break enum namespace static virtual
case explicit operator struct void
catch extern org switch volatile
char false pascal template while
class float private this
code for protected throw
const friend public true
continue goto register try
Ejemplos de los identificadores válidos e inválidos:

temperatura_V1 // OK
Presión // OK
no_corresponder // OK
dat2string // OK
SuM3 // OK
_vtexto // OK
7temp // NO -- no puede empezar con un número
%más_alto // NO -- no pueden contener caracteres especiales
if // NO -- no puede coincidir con una palabra reservada
j23.07.04 // NO -- no puede contener caracteres especiales (punto)
nombre de variable // NO -- no puede contener espacio en blanco

Declaración de variables

Cada variable debe ser declarada antes de ser utilizada en el programa. Como
las variables se almacenan en la memoria RAM, es necesario reservar el
espacio para ellas (uno, dos o más bytes). Al escribir un programa, usted sabe
qué tipo de datos quiere utilizar y qué tipo de datos espera como resultado de
una operación, mientras que el compilador no lo sabe. No se olvide de que el
programa maneja las variables con los nombres asignados. El compilador las
reconoce como números en la memoria RAM sin conocer su tamaño y formato.
Para mejorar la legibilidad de código, las variables se declaran con frecuencia
al principio de las funciones:

<tipo> variable;

Es posible declarar más de una variable de una vez si tienen el mismo tipo.

<tipo> variable1, variable2, variable3;

Aparte del nombre y del tipo, a las variables se les asignan con frecuencia los
valores iniciales justamente enseguida de su declaración. Esto no es un paso
obligatorio, sino ‘una cuestión de buenas costumbres’. Se parece a lo siguiente:

unsigned int peso; // Declarar una variable llamada peso


peso = 20; // Asignar el valor 20 a la variable peso

Un método más rápido se le denomina declaración con inicialización


(asignación de los valores iniciales):

unsigned int peso = 20; // peso está declarado y su valor es 20


Si hay varias variables con el mismo valor inicial asignado, el proceso se puede
simplificar:

unsigned int peso1 = peso2 = peso3 = 20;


int valor_inicial = un_mínimo_de_petróleo = 0;

 Tenga cuidado de no declarar la misma variable otra vez dentro de la


misma función.
 Puede modificar el contenido de una variable al asignarle un nuevo valor
tantas veces que quiera
 Al declarar una variable, siempre piense en los valores que la variable
tendrá que contener durante la ejecución de programa. En el ejemplo
anterior, peso1 no se puede representar con un número con punto
decimal o un número con valor negativo.
Declaración de constantes

Similar a las variables, las constantes deben ser declaradas antes de ser
utilizadas en el programa. En mikroC, no es obligatorio especificar el tipo de
constante al declararla. Por otra parte, las constantes deben ser inicializadas a
la vez que se declaran. El compilador reconoce las constantes por su prefijo
const utilizado en la declaración. Dos siguientes declaraciones son
equivalentes:

const int MINIMUM = -100; // Declarar constante MINIMUM


const MINIMUM = -100; // Declarar constante MINIMUM

Las constantes pueden ser de cualquier tipo, incluyendo cadenas:

const T_MAX = 3.260E1; // constante de punto flotante


T_MAX
const I_CLASS = 'A'; // constante carácter I_CLASS
const Mensaje = "Presione el botón IZQUIERDA"; // constante de cadena
Mensaje

Las constantes de enumeración son un tipo especial de constantes enteras que


hace un programa más comprensible al asignar los números ordinales a las
constantes. Por defecto, el valor 0 se asigna automáticamente a la primera
constante entre llaves, el valor 1 a la segunda, el valor 2 a la tercera etc.

enum surtidores {AGUA,GASÓLEO,CLORO}; // AGUA = 0; GASÓLEO = 1;


CLORO = 2

Es posible introducir directamente el valor de una constante dentro de la lista


de enumeraciones. El incremento se detiene al asignar un valor a un elemento
de matriz, después se reinicia a partir del valor asignado. Vea el siguiente
ejemplo:

enum surtidores {AGUA,GASÓLEO=0,CLORO}; // AGUA = 0; GÁSOLEO = 0;


CLORO = 1

Las constantes de enumeración se utilizan de la siguiente manera:

int Velocidad_de_ascensor
enum motor_de_ascensor {PARADA,INICIO,NORMAL,MÁXIMO};
Velocidad_de_ascensor = NORMAL; // Velocidad_de_ascensor = 2

Definir los nuevos tipos de datos

La palabra clave typedef le permite crear con facilidad los nuevos tipos de
datos.

typedef unsigned int positivo; // positivo es un sinónimo para el tipo sin signo
int
positivo a,b; // Variables a y b son de tipo positivo
a = 10; // Variable a equivale a 10
b = 5; // Variable b equivale a 5

Ámbito de variables y constantes

Una variable o una constante es reconocida por el compilador en base de su


identificador. Un identificador tiene significado si el compilador lo puede
reconocer. El ámbito de una variable o una constante es el rango de programa
en el que su identificador tiene significado. El ámbito es determinado por el
lugar en el que se declara una variable o una constante. Intentar acceder a una
variable o una constante fuera de su ámbito resulta en un error. Una variable o
una constante es invisible fuera de su ámbito. Todas las variables y constantes
que pensamos utilizar en un programa deben ser declaradas anteriormente en
el código. Las variables y constantes pueden ser globales o locales. Una
variable global se declara en el código fuente, fuera de todas las funciones,
mientras que una variable local se declara dentro del cuerpo de la función o
dentro de un bloque anidado en una función.
A las variables globales se les puede acceder de cualquiera parte en el código,
aún dentro de las funciones con tal de que sean declaradas. El ámbito de una
variable global está limitado por el fin del archivo fuente en el que ha sido
declarado.

El ámbito de variables locales está limitado por el bloque encerrado entre llaves
{} en el que han sido declaradas. Por ejemplo, si están declaradas en el
principio del cuerpo de función (igual que en la función main) su ámbito está
entre el punto de declaración y el fin de esa función. Refiérase al ejemplo
anterior. A las variables locales declaradas en main() no se les puede acceder
desde la Función_1 y al revés.
Un bloque compuesto es un grupo de declaraciones y sentencias (que pueden
ser bloques también) encerradas entre llaves. Un bloque puede ser una
función, una estructura de control etc. Una variable declarada dentro de un
bloque se considera local, o sea, ‘existe’ sólo dentro del bloque. Sin embargo,
las variables declaradas fuera del ámbito todavía son visibles.
Aunque las constantes no pueden ser modificadas en el programa, siguen las
mismas reglas que las variables. Esto significa que son visibles dentro de su
bloque a excepción de las constantes globales (declaradas fuera de cualquier
función). Las constantes se declaran normalmente en el inicio del código fuera
de cualquier función (como variables globales).

Clases de almacenamiento
Las clases de almacenamiento se utilizan para definir el ámbito y la vida de
variables, constantes y funciones dentro de un programa. En mikroC se pueden
utilizar diferentes clases de almacenamiento:

 auto es una clase de almacenamiento por defecto para las variables


locales, así que se utiliza raramente. Se utiliza para definir que una
variable local tiene duración local. La clase de almacenamiento auto no
se puede utilizar con variables globales.

 static es una clase de almacenamiento por defecto para las variables


globales. Especifica que una variable es visible dentro del archivo. A las
variables locales declaradas con el prefijo static se les puede acceder
dentro del archivo fuente (o sea se comportan como variables globales).
 extern: la palabra clave extern se utiliza cuando el programa está
compuesto por diferentes archivos fuente. Esto le permite utilizar una
variable, una constante o una función declarada en otro archivo. Por
supuesto, para compilar y enlazar este archivo correctamente, el mismo
debe ser incluido en su proyecto. En los siguientes ejemplos, el
programa consiste en dos archivos: File_1 y File_2. El File_1 utiliza una
variable y una función declaradas en File_2.
File 1:

extern int cnt; // Variable cnt es visible en File_1


extern void hello(); // Función hello()se puede utilizar en File_1

void main(){
PORTA = cnt++; // Cualquier modificación de cnt en File_1 será visible en
File_2
hello(); // Función hello()se puede llamar desde aquí
}

File 2:

int cnt = 0;
void hello();

void hello(){ // Modificaciones que afectan a la


. // cnt en File_1 son visibles aquí
.
.
}

2.5 OPERADORES

Un operador es un símbolo que denota una operación aritmética, lógica u otra


operación particular. Dicho de manera sencilla, varias operaciones aritméticas y
lógicas se realizan por medio de los operadores. Hay más de 40 operaciones
disponibles en el lenguaje C, pero se utiliza un máximo de 10-15 de ellas en
práctica. Cada operación se realiza sobre uno o más operandos que pueden
ser variables o constantes. Además, cada operación se caracteriza por la
prioridad de ejecución y por la asociatividad.

OPERADORES ARITMÉTICOS

Los operadores aritméticos se utilizan en las operaciones aritméticas y siempre


devuelven resultados numéricos. Hay dos tipos de operadores, los unitarios y
los binarios. A diferencia de las operaciones unitarias que se realizan sobre un
operando, las operaciones binarias se realizan sobre dos operandos. En otras
palabras, se requieren dos números para ejecutar una operación binaria. Por
ejemplo: a+b o a/b.

O P E R AD O R O P E R AC I Ó N
+ Adición
- Resta
* Multiplicación
/ División
% Resto de la división

int a,b,c; // Declarar 3 enteros a, b, c


a = 5; // Inicializar a
b = 4; // Inicializar b
c = a + b; // c = 9
c = c%2; // c = 1. Esta operación se utiliza con frecuencia
// para comprobar la paridad. En este caso, el
// resultado es 1 lo que significa que la variable
// es un número imparo

OPERADORES DE ASIGNACIÓN

Hay dos tipos de asignación en el lenguaje C:

 Los operadores simples asignan los valores a las variables utilizando el


carácter común '='. Por ejemplo: a =8
 Las asignaciones compuestas son específicas para el lenguaje C.
Consisten en dos caracteres como se muestra en la tabla a la derecha.
Se utilizan para simplificar la sintaxis y habilitar la ejecución más rápida.
EJEMPLO
O P E R AD O R
Expresión Equivalente
+= a += 8 a=a+8
-= a -= 8 a=a-8
*= a *= 8 a=a*8
/= a /= 8 a=a/8
%= a %= 8 a=a%8

int a = 5; // Declarar e inicializar la variable a


a += 10; // a = a + 10 = 15

OPERADORES DE INCREMENTO Y DECREMENTO

Las operaciones de incremento y decremento por 1 se denotan con "++" y "--".


Estos caracteres pueden preceder o seguir a una variable. En primer caso
(++x), la variable x será incrementada por 1 antes de ser utilizada en la
expresión. De lo contrario, la variable se utilizará en la expresión antes de ser
aumentada por 1. Lo mismo se aplica a la operación de decremento.

O P E R AD O R EJEMPLO DESCRIPCIÓN
++a
++ Variable "a" es incrementada por 1
a++
--b
-- Variable "a" es decrementada por 1
b--

int a, b, c;
a = b = 5;
c = 1 + a++; // c = 6
b = ++c + a // b = 7 + 6 = 13

OPERADORES RELACIONALES

Los operadores relacionales se utilizan en comparaciones con el propósito de


comparar dos valores. En mikroC, si una expresión es evaluada como falsa
(false), el operador devuelve 0, mientras que si una oración es evaluada como
verdadera (true), devuelve 1. Esto se utiliza en expresiones tales como ‘si la
expresión es evaluada como verdadera, entonces...’

O P E R AD O R DESCRIPCIÓN EJEMPLO C O N D I C I Ó N D E V E R AC I D AD
> mayor que b>a si b es mayor que a
>= mayor o igual que a >= 5 si a es mayor o igual que 5
< menor que a<b si a es menor que b
<= menor o igual que a <= b si a es menor o igual que b
== igual que a == 6 si a es igual que 6
!= desigual que a != b si a es desigual que b

int prop;
int var = 5;
prop = var < 10; // Expresión es evaluada como verdadera, prop = 1

OPERADORES LÓGICOS

Hay tres tipos de operaciones lógicas en el lenguaje C: Y (AND) lógico, O (OR)


lógico y negación - NO (NOT) lógico. Los operadores lógicos devuelven
verdadero (1 lógico) si la expresión evaluada es distinta de cero. En caso
contrario, devuelve falso (0 lógico) si la expresión evaluada equivale a cero.
Esto es muy importante porque las operaciones lógicas se realizan
generalmente sobre las expresiones, y no sobre las variables (números)
particulares en el programa. Por lo tanto, las operaciones lógicas se refieren a
la veracidad de toda la expresión.

Por ejemplo: 1 && 0 es igual a (expresión verdadera) && (expresión falsa)


El resultado 0, o sea - Falso en ambos casos.

O P E R AD O R FUNCIÓN
&& Y
|| O
! NO
OPERADORES DE MANEJO DE BITS

A diferencia de las operaciones lógicas que se realizan sobre los valores o


expresiones, las operaciones de manejo de bits se realizan sobre los bits de un
operando. Se enumeran en la siguiente tabla:

O P E R AD O R DESCRIPCIÓN EJEMPLO R E S U L T AD O
~ Complemento a uno a = ~b b=5 a = -5
<< Desplazamiento a la izquierda a = b << 2 b = 11110011 a = 11
>> Desplazamiento a la derecha a = b >> 2 b = 11110011 a = 00
a = 11100011
& Y lógico para manejo de bits c=a&b c = 11
b = 11001100
| O lógico para manejo de bits c=a|b a = 11100011 c = 11
b = 11001100
a = 11100011
^ EXOR lógico para manejo de bits c=a^b c = 00
b = 11001100
Note que el resultado de la operación de desplazamiento a la derecha depende
del signo de la variable. En caso de que el operando se aplique a una variable
sin signo o positiva, se introducirán los ceros en el espacio vacío creado por
desplazamiento. Si se aplica a un entero con signo negativo, se introducirá un 1
para mantener el signo correcto de la variable.
¿CÓMO UTILIZAR LOS OPERADORES?
 Aparte de los operadores de asignación, dos operadores no deben estar
escritos uno junto al otro.

x*%12; // esta expresión generará un error

 Cada operador tiene su prioridad y asociatividad como se muestra en la


tabla:
 Similar a las expresiones aritméticas, los operadores se agrupan juntos
por medio de paréntesis. Primero se calculan las expresiones
encerradas entre paréntesis. Si es necesario, se pueden utilizar los
paréntesis múltiples (anidados).
P R I O R I D AD O P E R AD O R E S A S O C I AT I V I D
Alta () [] -> . de izquierda a der
! ~ ++ -- +(unitario) -(unitario) *Puntero &Puntero de derecha a izqu
*/% de izquierda a der
+- de izquierda a der
<> de izquierda a der
< <= > >= de izquierda a der
== != de izquierda a der
& de izquierda a der
^ de izquierda a der
| de izquierda a der
&& de izquierda a der
|| de derecha a izqu
?: de derecha a izqu
Baja = += -= *= /= /= &= ^= |= <= >= de izquierda a der

int a, b, res;
a = 10;
b = 100;
res = a*(a + b); // resultado = 1100
res = a*a + b; // resultado = 200

CONVERSIÓN DE TIPOS DE DATOS

Algunas operaciones implican conversión de datos. Por ejemplo, si divide dos


valores enteros, hay una alta posibilidad de que el resultado no sea un entero.
El mikroC realiza una conversión automática cuando se requiera.

Si dos operandos de tipo diferente se utilizan en una operación aritmética, el


tipo de operando de la prioridad más baja se convierte automáticamente en el
tipo de operando de la prioridad más alta. Los tipos de datos principales se
colocan según el siguiente orden jerárquico:

La autoconversión se realiza asimismo en las operaciones de asignación. El


resultado de la expresión de la derecha del operador de la asignación siempre
se convierte en el tipo de la variable de la izquierda del operador. Si el
resultado es de tipo de la prioridad más alta, se descarta o se redondea para
coincidir con el tipo de la variable. Al convertir un dato real en un entero,
siempre se descartan los números que siguen al punto decimal.

int x; // A la variable x se le asigna el tipo integer (un entero)


x = 3; // A la variable x se le asigna el valor 3
x+ = 3.14; // El valor 3.14 se agrega a la variable x al
// realizar la operación de asignación

/* El resultado de la adición es 6 en vez de 6.14, como era de esperar.


Para obtener el resultado esperado sin descartar los números que siguen al
punto decimal, se debe declarar x como un punto flotante. */

Para realizar una conversión explícita, antes de escribir una expresión o una
variable hay que especificar el tipo de resultado de operación entre paréntesis.

double distancia, tiempo, velocidad;


distancia = 0.89;
tiempo = 0.1;
velocidad = (int)(a/b); // c = (int)8.9 = 8.0
velocidad = ((int)a)/b; // c = 0/0.1 = 0.0

2.6 ESTRUCTURAS DE CONTROL


ESTRUCTURAS CONDICIONALES

Las condiciones son ingredientes comunes de un programa. Las condiciones


permiten ejecutar una o varias sentencias dependiendo de validez de una
expresión. En otras palabras, ‘Si se cumple la condición (...), se debe hacer
(...). De lo contrario, si la condición no se cumple, se debe hacer (...)’. Los
operandos condicionales if-else y switch se utilizan en las operaciones
condicionales. Una sentencia condicional puede ser seguida por una sola
sentencia o por un bloque de sentencias a ser ejecutadas.
OPERADOR CONDICIONAL if-else

El operador if se puede utilizar solo o asociado al operador else (if-else).


Ejemplo del operador if:

if(expresión) operación;

Si el resultado de la expresión encerrada entre paréntesis es verdadero


(distinto de 0) la operación se realiza y el programa continúa con la ejecución.
Si el resultado de la expresión es falso (0), la operación no se realiza y el
programa continúa inmediatamente con la ejecución.
Como hemos mencionado, la otra forma combina tanto el operador if como el
else:

if(expresión) operación1 else operación2;

Si el resultado de la expresión es verdadero (distinto de 0), se


realiza operación1, de lo contrario se realiza la operación2. Después de realizar
una de las operaciones, el programa continúa con la ejecución.
La sentencia if-else se parece a lo siguiente:

if(expresión)
operación1
else
operación2

Si operación1 u operación2 está compuesta, escriba una lista de sentencias


encerradas entre llaves. Por ejemplo:

if(expresión) {
... //
... // operación1
...} //
else
operación2

El operador if-else se puede sustituir por el operador condicional '?:':

(expresión1)? expresión2 : expresión3

Si el valor de la expresión1 es distinto de 0 (verdadero), el resultado de la


expresión entera será equivalente al resultado obtenido de laexpresión2. De lo
contrario, si la expresión1 es 0 (falso), el resultado de la expresión entera será
equivalente al resultado obtenido de laexpresión3. Por ejemplo:

maximum = (a>b)? a : b // A la variable maximum se le asigna el


// valor de la variable mayor(a o b)

Operador Switch

A diferencia de la sentencia if-else que selecciona entre dos opciones en el


programa, el operador switch permite elegir entre varias opciones. La sintaxis
de la sentencia switch es:

switch (selector) // Selector es de tipo char o int


{
case constante1:
operación1 // El grupo de operadores que se ejecutan si
... // el selector y la constante1 son equivalentes

break;

case constante2:

operación2 // El grupo de operadores se ejecuta si


... // el selector y la constante2 son equivalentes

break;
...
default:

operación_esperada // El grupo de operadores que se ejecuta si


... // ninguna constante equivale al selector
break;
}
La operación switch se ejecuta de la siguiente manera: primero se ejecuta el
selector y se compara con la constante1. Si coinciden, las sentencias que
pertenecen a ese bloque se ejecutan hasta llegar a la palabra clave break o
hasta el final de la operación switch. Si no coinciden, el selector se compara
con la constante2. Si coinciden, las sentencias que pertenecen a ese bloque se
ejecutan hasta llegar a la palabra clave break etc. Si el selector no coincide con
ninguna constante, se ejecutarán las operaciones que siguen al operador
default.

También es posible comparar una expresión con un grupo de constantes. Si


coincide con alguna de ellas, se ejecutarán las operaciones apropiadas:

switch (días) // La variable días representa un día de la semana.


{ // Es necesario determinar si es un día laborable o no lo es
case1:case2:case3:case4:case5: LCD_message = 'Día laborable'; break;
case6:case7: LCD_message = 'Fin de semana'; break;
default:LCD_message_1 = 'Elija un día de la semana'; break;
}

La palabra clave de C ‘break’ se puede utilizar en cualquier tipo de bloques. Al


utilizar ‘break’, es posible salir de un bloque aunque la condición para su final
no se haya cumplido. Se puede utilizar para terminar un bucle infinito, o para
forzar un bucle a terminar antes de lo normal.
BUCLES

A menudo es necesario repetir una cierta operación un par de veces en el


programa. Un conjunto de comandos que se repiten es denominado un bucle
de programa. Cuántas veces se ejecutará, es decir cuánto tiempo el programa
se quedará en el bucle, depende de las condiciones de salir del bucle.

Bucle While

El bucle while se parece a lo siguiente:

while(expresión){
comandos
...
}

Los comandos se ejecutan repetidamente (el programa se queda en el bucle)


hasta que la expresión llegue a ser falsa. Si la expresión es falsa en la entrada
del bucle, entonces el bucle no se ejecutará y el programa continuará desde el
fin del bucle while.
Un tipo especial del bucle de programa es un bucle infinito. Se forma si la
condición sigue sin cambios dentro del bucle. La ejecución es simple en este
caso ya que el resultado entre llaves es siempre verdadero (1=verdadero), lo
que significa que el programa se queda en el mismo bucle:

while(1){ // En vez de "while(1)", se puede escribir "while(true)"


... // Expresiones encerradas entre llaves se ejecutarán
... // repetidamente (bucle infinito)
}

Bucle For

El bucle for se parece a lo siguiente:

for(expresión_inicial; expresión_de_condición; cambiar_expresión) {


operaciones
...
}

La ejecución de esta secuencia de programa es similar al bucle while, salvo


que en este caso el proceso de especificar el valor inicial (inicialización) se
realice en la declaración. La expresión_ inicial especifica la variable inicial del
bucle, que más tarde se compara con la expresión_ de_condición antes de
entrar al bucle. Las operaciones dentro del bucle se ejecutan repetidamente y
después de cada iteración el valor de la expresión_inicial se incrementa de
acuerdo con la regla cambiar_expresión. La iteración continúa hasta que la
expresión_de_condición llegue a ser falsa.

for(k=0; k<5; k++) // La variable k se incrementa 5 veces (de 1 a 4) y


operación // cada vez se repite la expresión operación
...

La operación se ejecutará cinco veces. Luego, al comprobar se valida que la


expresión k<5 sea falsa (después de 5 iteraciones k=5) y el programa saldrá
del bucle for.

Bucle Do-while

El bucle do-while se parece a lo siguiente:

do
operación
while (cambiar_condición);

La expresión cambiar_condición se ejecuta al final del bucle, que significa que


operación se ejecuta como mínimo una vez sin reparar en que si la condición
es verdadera o falsa. Si el resultado es distinto de 0 (verdadero), el
procedimiento se repite.

Todos los siguientes ejemplos son equivalentes. Esta parte del código visualiza
"hello" en un LCD 10 veces con un retardo de un segundo. Note que en este
ejemplo se utilizan funciones predefinidas, que se encuentran en las librerías
del compilador mikroC PRO for PIC. No obstante le aconsejamos que no trate
de entenderlas en detalle. Su comportamiento general dentro del bucle se
explica por medio de los comentarios.

i = 0; // Inicialización del contador

while (i<10) { // Condición


Lcd_Out(1,3,"hello"); // Visualizar “hello” en el LCD
Delay_ms(1000); // Retardo de 1000 ms
Lcd_Cmd(_LCD_CLEAR); // Borrar el LCD
Delay_ms(500); // Retardo de 500ms
i++; // Contador se incrementa
}
for(i=0; i<10; i++) { // Inicialización, condición, incremento
Lcd_Out(1,3,"hello"); // Visualizar “hello” en el LCD
Delay_ms(1000); // Retardo de 1000 ms
Lcd_Cmd(_LCD_CLEAR); // Borrar el LCD
Delay_ms(500); // Retardo de 500ms
}
i = 0; // Inicialización del contador
do {
Lcd_Out(1,3,"hello"); // Visualizar “hello” en el LCD
Delay_ms(1000); // Retardo de 1000 ms
Lcd_Cmd(_LCD_CLEAR); // Borrar LCD
Delay_ms(500); // Retardo de 500ms
i++; // Contador se incrementa
}
while (i<10); // Condición

SENTENCIAS DE SALTO
SENTENCIA BREAK

A veces es necesario detener y salir de un bucle dentro de su cuerpo. La


sentencia break se puede utilizar dentro de cualquier bucle (while, for, do while)
y en las sentencias switch también. En éstas la sentencia break se utiliza para
salir de las sentencias switch si la condición case es verdadera. En este
ejemplo, “Esperar” está parpadeando en la pantalla LCD hasta que el programa
detecte un uno lógico en el pin 0 del puerto PORTA.

while(1){ // Bucle infinito


if(PORTA.F0 == 1) // Probar si el estado lógico del pin 0 del puerto
break; // PORTA es 1; si equivale, salir del bucle
Lcd_Out(1,3,"Esperar"); // Visualizar “Esperar” en el LCD
Delay_ms(1000); // Retardo de 1000 ms
Lcd_Cmd(_LCD_CLEAR); // Borrar LCD
Delay_ms(500); // Retardo de 500ms
}

SENTENCIA CONTINUE

La sentencia continue colocada dentro de un bucle se utiliza para saltar una


iteración. A diferencia de la sentencia break, el programa se queda dentro del
bucle y las iteraciones continúan.

// Si x=7, puede ocurrir una división por 0.


// continue se utiliza aquí para evitar esta situación.
x=1;
while (x<=10) {
if (x == 7) { // saltar x=7 para evitar división por 0
Lcd_Cmd(_LCD_CLEAR);
Lcd_Out(1,3,"Division by 0");
Delay_ms(1000);
x++;
continue; // Después de esta línea, saltar a la sentencia while con x=8
}

a = 1/(x-7); // Esta división generará un error si x=7

/* Muchas operaciones pueden ocurrir aquí */

Lcd_Out(1,3,"Division is OK"); // Poner este mensaje en el LCD


Delay_ms(1000);
x++;
}

SENTENCIA GOTO

La sentencia goto le permite hacer un salto absoluto al otro punto en el


programa. Esta característica se debe utilizar con precaución ya que su
ejecución puede causar un salto incondicional sin hacer caso a todos los tipos
de limitaciones de anidación. El punto destino es identificado por una etiqueta,
utilizada como un argumento para la sentencia goto. Una etiqueta consiste en
un identificador válido seguido por un colon (:).

...
if(CO2_sensor) goto aire acondicionado; // Si se consta que el valor
... // de la variable CO2_sensor =1
// hacer salto a la línea de programa
// Aire acondicionado
...
Aire acondicionado: // Desde aquí sigue la parte del código que
se ejecutará
// en caso de una concentración de CO2 demasiado
alta
... // en el ambiente

2.7 TIPOS DE DATOS AVANZADOS


MATRICES

Una matriz es una lista de elementos del mismo tipo colocados en localidades
de memoria contiguas. Cada elemento es referenciado por un índice. Para
declarar una matriz, es necesario especificar el tipo de sus elementos
(denominado tipo de matriz), su nombre y el número de sus elementos
encerrados entre corchetes. Todos los elementos de una matriz tienen el
mismo tipo.

tipo_de_matriz nombre_de_matriz [nº_de_elementos];

Los elementos de una matriz se identifican por su posición. En C, el índice va


desde 0 (el primer elemento de una matriz) a N-1 (N es el número de
elementos contenidos en una matriz). El compilador tiene que “saber” cuántas
localidades de memoria debe alojar al declarar una matriz. El tamaño de una
matiz no puede ser una variable. Por eso, se pueden utilizar dos métodos:

// método 1
int display [3]; // Declaración de la matriz display capaz de contener 3 enteros
// método 2
const DÍGITOS = 5;
char Matriz_nueva[DÍGITOS]; // Declaración de la matriz Matriz_nueva
// capaz de contener 5 enteros

Una matriz se puede inicializar a la vez que se declara, o más tarde en el


programa. En ambos casos, este paso se realiza al utilizar llaves:
int array_1[3] = {10,1,100};

Para leer o modificar un elemento de matriz del ejemplo anterior, basta con
introducir su índice encerrado entre corchetes:

/* Se supone que a ha sido declarado anteriormente como un entero */

a = array_1[0]; // A la variable a se le asigna el valor del miembro de matriz

// con índice 0 (a = 10)


array_1[2] = 20; // Miembro de matriz array_1[2] es modificado (nuevo valor es
20)

El siguiente programa cambia el orden de los elementos de una matriz. Note


que el índice se puede expresar mediante variables y operaciones básicas.

void main() {
const MUESTRAS_DE_AGUA = 4; // Valor de la constante
MUESTRAS_DE_AGUA es 4
int i, temp; // Variables i y temp son de tipo int
int profunidad_de_sonda [MUESTRAS_DE_AGUA] = {24,25,1,1987};//
Todos

// los miembros de la matriz profundidad


// de sonda son de tipo int

for(i=0;i<(MUESTRAS_DE_AGUA/2);i++){ // Bucle se ejecuta 2 veces


temp = profundiad_de_sonda [i]; // temp se utiliza para guardar un valor
// temporalmente
profundiad_de_sonda [i] = profundiad_de_sonda [MUESTRAS_DE_AGUA-
1-i];
profundiad_de_sonda [MUESTRAS_DE_AGUA-1-i] = temp;
}

// Aquí tenemos: profundidad_de_sonda [MUESTRAS_DE_AGUA] =


{1987,1,25,24}
}

MATRICES BIDIMENSIONALES

Aparte de las matrices unidimensionales que se pueden interpretar como una


lista de valores, el lenguaje C le permite declarar matrices multidimensionales.
En esta parte vamos a describir sólo las matrices bidimensionales, también
denominadas tablas o matrices. Una matriz bidimensional se declara al
especificar el tipo de dato de matriz, el nombre de matriz y el tamaño de cada
dimensión.
tipo_de_matriz nombre_de_matriz [número_de_filas]
[número_de_columnas];

En la declaración de esta matriz número_de_filas y número_de_columnas


representan el número de filas y columnas en las que consiste una tabla,
respectivamente. Vea la siguiente matriz bidimensional:

int Tabla [3][4]; // Tabla se define de modo que tenga 3 filas y 4 columnas

Esta matriz se puede representar en la forma de una tabla.

tabla[0][0] tabla[0][1] tabla[0][2] tabla[0][3]


tabla[1][0] tabla[1][1] tabla[1][2] tabla[1][3]
tabla[2][0] tabla[2][1] tabla[2][2] tabla[2][3]
Similar a las matrices unidimesionales, es posible asignar los valores a los
elementos de una tabla en la línea de declaración. La asignación debe ser
realizada línea a línea como en el siguiente ejemplo. Como hemos visto
anteriormente, esta matriz tiene dos filas y tres columnas:

int Tabla [2][3]= { {3,42,1},{7,7,19} };

La matriz anterior se puede representar también en la forma de una tabla de


valores:

3 42 1
7 7 19
PUNTEROS

Un puntero es una variable destinada a recibir una dirección. Un puntero


“apunta” a una localidad de memoria, referenciada por una dirección. En C, la
dirección de un objeto se puede obtener por medio un operador unitario &. Para
acceder al contenido de la memoria en una dirección específica (también
llamado objeto apuntado), se utiliza un operador de indirección (*).

'&n' es la dirección de la localidad de memoria 'n'.


'*(&n)' es el contenido de la dirección '(&n)', o sea de 'n'.

Para declarar un puntero, se debe que especificar el tipo de la variable


apuntada:

tipo_de_variable *puntero;

En esta etapa, el puntero mi_puntero apunta al valor almacenado en esta


localidad de memoria, o sea, a un valor desconocido. Así que, una inicialización
es muy recomendable:

puntero = &variable;

Ahora, puntero contiene la dirección de variable.


Para acceder al contenido de la variable apuntada, debe utilizar ‘*’. El siguiente
ejemplo muestra el contenido de memoria dependiendo de la acción realizada
por medio del puntero.

Los punteros son muy útiles para manejar las matrices. En este caso, un
puntero se utilizará para apuntar al primer elemento de una matriz. Debido al
hecho de que es posible realizar operaciones básicas sobre los punteros
(aritmética de punteros), es fácil manejar los elementos de una matriz.

Fíjese en la diferencia entre ‘*v+1’ y ‘*(v+1)’ en el siguiente ejemplo:

short int voltio[3] = {0,5,10};


short int *v;
v = &(voltio[0]); // v contiene la dirección de voltio[0]
*(v+1) = 2; // voltio[1] = 2
voltio[2] = *v+1; // tab[2] = 1 (tab[0] + 1)
*(v+2) = *(v+1); // voltio[2] = 2
v++; // v contiene la dirección de voltio[1]
*v = 1; // voltio[1] = 1

Los punteros también pueden ser declarados con el prefijo ‘const’. En


este caso, su valor no puede ser modificado después de la inicialización,
similar a una constante.
 A diferencia de C, el mikroC no admite alojamiento dinámico.
ESTRUCTURAS

Ya hemos visto cómo agrupar los elementos dentro de matrices. No obstante,


al utilizar este método todos los elementos deben ser del mismo tipo. Al utilizar
estructuras, es posible agrupar diferentes tipos de variables bajo el mismo
nombre. Las variables dentro de una estructura se le denominan los miembros
de la estructura. Las estructuras de datos se declaran al utilizar la siguiente
sintaxis:

struct nombre_de_estructura {
tipo1_de_miembro1 miembro1;
tipo2_de_miembro2 miembro2;
tipo3_de_miembro3 miembro3;
..
};

No es posible inicializar variables dentro de la declaración de la estructura de


datos:

struct generador {
int voltaje;
char corriente;
};

Entonces, podrá definir los objetos denominados ‘turbina’ en el código. A cada


uno de estos tres objetos (turbinas) se le asignan las variables ‘corriente’ y
‘voltaje’.

struct generadores turbina_1, turbina_2, turbina_3;

Para acceder a las variables, es preciso utilizar el operador '.'

turbina_3.voltaje = 150;
turbina_3.corriente = 12;

Por supuesto, igual que al utilizar los punteros, todavía se le permite realizar
operaciones por medio de operadores y sentencias definidos en las partes
anteriores.

Si está familiarizado con el lenguaje C, recuerde que mikroC no admite la


inicialización de los miembros de estructura por medio de las llaves. Por
ejemplo, ‘conjunto_1 ={15,‘m’};’ devuelve un error en mikroC.
2.8 FUNCIONES

Una función es una subrutina que contiene una lista de sentencias a realizar.
La idea principal es dividir un programa en varias partes utilizando estas
funciones para resolver el problema inicial con más facilidad. Además, las
funciones nos permiten utilizar las destrezas y el conocimiento de otros
programadores. Una función se ejecuta cada vez que se llame dentro de otra
función. En C, un programa contiene como mínimo una función, la función
main(), aunque el número de funciones es normalmente mayor. Al utilizar
funciones el código se hace más corto ya que es posible llamar una función
tantas veces como se necesite. En C, el código normalmente consiste en
muchas funciones. No obstante, en caso de que su programa sea muy corto y
simple, puede escribir todas las sentencias dentro de la función principal.

FUNCIÓN PRINCIPAL

La función principal main() es una función particular puesto que es la que se


ejecuta al iniciar el programa. Además, el programa termina una vez
completada la ejecución de esta función. El compilador reconoce
automáticamente esta función y no es posible llamarla por otra función. La
sintaxis de esta función es la siguiente:

void main (void) {

/* el primer 'void' significa que main no devuelve ningún valor. El segundo


'void' significa que no recibe ningún valor. Note que el compilador
también admite la siguiente sintaxis: 'main()' o 'void main()' o
'main(void)' */

..

/* --- Introduzca su programa aquí --- */


.
};

Esto significa que f es una función que recibe un número real x como
parámetro y devuelve 2*x-y.

La misma función en C se parece a lo siguiente:

float f (float x, float y) // variables flotantes x y y se pueden utilizar en f


{
float r; // declarar r para almacenar el resultado
r = 2*x - y; // almacenar el resultado del cálculo en r
return r; // devolver el valor de r
}

Cada función debe ser declarada apropiadamente para poder interpretarla


correctamente durante el proceso de compilación. La declaración contiene los
siguientes elementos:

Tipo de resultado (valor devuelto): tipo de dato del valor devuelto


Nombre de función: es un identificador que hace posible llamar a una
función.
 Declaración de parámetros se parece a la declaración de variable
regular (por ejemplo: float x). Cada parámetro consiste en una variable,
constante, puntero o matriz, precedidos por la etiqueta de tipo de dato.
Se utilizan para pasar la información a la función al llamarla. Los
parámetros diferentes están delimitados por comas.
 Cuerpo de función: bloque de sentencias dentro de llaves
Una función se parece a lo siguiente:

tipo_de_resultado nombre_de_función (tipo argumento1, tipo


argumento2,...)
{
Sentencia;
Sentencia;
...
return ...
}

Note que una función no necesita parámetros (función main() por ejemplo),
pero debe estar entre paréntesis. En caso contrario, el compilador
malinterpretaría la función. Para hacerlo más claro, puede sustituir el espacio
en blanco encerrado entre paréntesis por la palabra clave void: main (void).
VALOR DEVUELTO
Una función puede devolver un valor (esto no es obligatorio) por medio de la
palabra clave return. Al llegar a return, la función evalúa un valor (puede ser
una expresión) y lo devuelve a la línea de programa desde la que fue llamada.

return r; // Devolver el valor contenido en r


return (2*x - y); // Devolver el valor de la expresión 2*x-y

Una función no puede devolver más de un valor, pero puede devolver un


puntero o una estructura. Tenga cuidado al utilizar matrices y punteros. El
siguiente ejemplo es un error típico:

int *reverse(int *tab) // Esta función debe devolver una matriz r


{ // cuyo contenido está en orden inverso con
// respecto a la matriz tab
int r[DIM]; // Declaración de una nueva matriz denominada r
int i;
for(i=0;i<DIM;i++) // Bucle que copia el contenido de tab en r
r[i] = tab[DIM-1-i]; // al invertir el orden

return r; // Devolver el valor r


}

En realidad, el compilador reserva memoria para el almacenamiento de


variables de la función reverse sólo durante su ejecución. Una vez completada
la ejecución de reverse, la localidad de memoria para la variable i o para la
matriz r ya no está reservada. Esto significa que la dirección que contiene los
valores de i o r[] está libre para introducir datos nuevos. Concretamente, la
función devuelve sólo el valor &r[0], así que sólo el primer elemento de la
matriz tab será almacenado en la memoria. Las demás localidades de
memoria, tales como &tab[1], &tab[2], etc. serán consideradas por el
compilador como espacios en blanco, o sea, estarán listas para recibir los
nuevos valores.

Para escribir esta función es necesario pasar la matriz r [] como parámetro (vea
la subsección Pasar los parámetros).

La función puede contener más de una sentencia return. En este caso, al


ejecutar la primera sentencia return, la función devuelve el valor
correspondiente y se detiene la ejecución de la función.

float abs (float x, float y) // Devolver el valor absoluto de 2*x-y


{
if ((2*x - y) >= 0)
return (2*x - y);
else
return (-2*x + y);
}

Si la función no devuelve ningún valor, la palabra void debe ser utilizada como
un tipo de resultado en la declaración. En este caso, la sentencia return no
debe ser seguida por ninguna expresión. Puede ser omitida como en el
siguiente ejemplo:

void wait_1 (unsigned int a)


{
cnt ++; // Incremento de una variable global cnt
Delay_ms(a) ; // Ejecución de la función Delay_ms
} // Note que Delay_ms no devuelve nada

DECLARAR PROTOTIPOS DE FUNCIONES

Para utilizar una función, el compilador debe ser consciente de su presencia en


el programa. En la programación en C, los programadores normalmente
primero escriben la función main() y luego las funciones adicionales. Para
avisar al compilador de la presencia de las funciones adicionales, se requiere
declarar los prototipos de funciones en el principio de programa antes de la
funciónmain(). Un prototipo de función está compuesto por:
 tipo de resultado
 nombre de función
 tipos de parámetros
 un punto y coma (;)
El prototipo de la función main no necesita ser declarado.

float f (float, float);

/* no es obligatorio escribir los nombres de los parámetros. Este prototipo


informa al compilador: en el programa se utilizará la función f,
que utiliza dos parámetros de tipo float y devuelve el resultado del tipo
float. */

LLAMAR UNA FUNCIÓN

Mientras una función es definida y su prototipo declarado, se puede utilizar en


culquier parte de programa. Sin embargo, como la funciónmain es 'raiz' del
programa, no puede ser llamada de ninguna parte de programa. Para ejecutar
una función, es necesario escribir su nombre y los parámetros asociados. Vea
los siguientes ejemplos:
float resultado,a,b; // resultado,a,b,time deben coincidir con los tipos
// definidos
int time = 100; // en la declaración de las funciones f y wait_1
a = 10.54;
b = 5.2;
resultado = f(a,b); // Ejecutar la función f por medio de los parámetros a y b

// El valor devuelto se le asigna a la variable resultado


pausa_1(tiempo); // Ejecutar la función pausa_1 por medio de la variable
tiempo
funciónX(); // Ejecutar la función funciónX (sin parámetros)

Cuando se llama una función, el programa salta a la función llamada, la


ejecuta, después vuelve a la línea desde la que fue llamada.

PASAR LOS PARÁMETROS

Al llamar una función, se le pasan los parámetros. En C existen dos formas


diferentes para pasar parámetros a una función.

El primer método, denominado ‘paso por valor’, es el más fácil. En este caso,
los parámetros se pueden considerar como variables locales de la función.
Cuando se llama una función, el valor de cada parámetro se copia a un nuevo
espacio de memoria reservado durante la ejecución de la función. Como los
parámetros se consideran como variables locales por el compilador, sus
valores pueden ser modificados dentro de la función, pero sus modificaciones
no se quedan en la memoria una vez completada la ejecución de la función.

Tenga en cuenta de que la función devuelve un valor, y no una variable.


Además, se crean copias de los valores de los parámetros, por lo que sus
nombres en la función f pueden ser diferentes de los parámetros utilizados en
la main(). La mayor desventaja del ‘paso por el valor’ es que la única
interacción que una función tiene con el resto del programa es el valor devuelto
de un solo resultado (o la modificación de las variables globales).
El otro método, denominado 'paso por dirección' le permite sobrepasar este
problema. En vez de enviar el valor de una variable al llamar a función, se debe
enviar la dirección de memoria del valor. Entonces, la función llamada será
capaz de modificar el contenido de esta localidad de memoria.

// Función 'sort'ordena los miembros de la matriz por valor ascendente


// y devuelve el miembro con máximo valor

int sort(int *); // Prototipo de función


const SIZE = 5; // Número de miembros a ordenar

void main() {
int maximum, input[SIZE] = {5,10,3,12,0}; // Declaración de variables en la
matriz
maximum = sort(input); // Llamar a función y asignarle el
máximo
// valor a la variable maximum
}

int sort(int *sequence) {


int i, temp, permut; // Declaración de variables
permut = 1; // Bandera de bit indica que se ha hecho una
permutación

while(permut!=0) { // Quedarse en el bucle hasta reinicar la bandera


permut = 0; // Bandera reiniciada
for(i=0;i<SIZE-1;i++) { // Comparar y oredenar los miembros de la
// matriz (dos a dos)
if(sequence [i] > sequence[i+1]){
temp = sequence [i];
sequence[i] = sequence[i+1];
sequence[i+1] = temp;
permut = 1; // Se ha hecho una permutación, bandera de bit
//se pone a uno
}
}
}

return sequence[SIZE-1]; // Devolver el valor del último miembro

} // que es al mismo tiempo el miembro con el máximo valor

En este ejemplo, por medio de una función se realizan dos operaciones: ordena
los miembros de la matriz por valor asdendente y devuelve el máximo valor.

Para utilizar una matriz en una función es necesario asignar la dirección a la


matriz (o a su primer miembro). Vea el siguiente ejemplo:

float método_1(int[]); // Declaración de prototipo de la función Método_1


float método_2(int*); // Declaración de prototipo de la función Método_2

const NÚMERO_DE_MEDICIONES = 7; // Número de los miembros de la


matriz

void main()
{
double promedio1, promedio2; // Declaración de las variables promedio1
// y promedio2
int voltaje [NÚMERO_DE_MEDICIONES] = {7,8,3,5,6,1,9}; // Declaración de
la
// matriz voltaje
promedio1 = método_1(&voltaje[0]); // Parámetro de la función es la
dirección
// del primer miembro
promedio2 = método_2(voltaje); // Parámetro de la función es la dirección de
// la matriz
}

//××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
××××××××××
float método_1(int voltaje[]) // Inicio de la función método_1
{
int i, suma; // Declaración de las variables locales i y suma

for(i=0;i<NÚMERO_DE_MEDICIONES;i++) // Cálculo del valor promedio de


voltaje
suma += voltaje[i]; // Es posible utilizar *(voltaje+i)en vez de
voltaje[i]

return(suma/NÚMERO_DE_MEDICIONES);
}

//××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
××××××××××
float método_2 (int *voltaje) //Inicio de la función método_2
{
int i, suma; // Declaración de las variables locales i y suma

for(i=0;i<NÚMERO_DE_MEDICIONES;i++) // Cálculo del valor promedio de


voltaje
suma += *(voltaje+i); // Es posible utilizar voltaje[i] en vez de
*(voltaje+i)

return(suma/NÚMERO_DE_MEDICIONES);
}

Las funciones 'método_1' y 'método_2' son completamente equivalentes. Las


dos devuelven el valor promedio de la matriz 'voltaje[]'. Después de declararla,
la dirección del primer miembro se puede escribir como 'voltaje' o '&voltaje[0]'.

2.9 CARACTERÍSTICAS PRINCIPALES DEL PREPROCESADOR

El preprocesador es un programa que procesa el código antes de que pase por


el compilador. Funciona bajo el control de las líneas de comando del
preprocesador denominadas directivas. Las directivas del preprocesador se
colocan en el código fuente, normalmente en el principio del archivo. Antes de
pasar por el compilador, el código fuente se examina por el preprocesador que
detecta y ejecuta todas las directivas del preprocesador. Las directivas del
preprocesador siguen a una regla de la sintaxis especial, empiezan por un
símbolo ‘#’ y no requieren ningún punto y coma al final (;).

DIRECTIVAS DEL PREPROCESADOR

La siguiente tabla contiene un conjunto de directivas del preprocesador


frecuentemente utilizadas:

Las directivas del preprocesador se pueden dividir en tres categorías:

D I R E C T I V AS FUNCIONES
#include Define una sustitución de macro
#undef Quita una definición de nombre de macro
#define Especifica un archivo a ser incluido
#ifdef Prueba para definición de macro
#endif Especificar el final de #if
#ifndef Prueba si una macro no está definida
#if Prueba las condiciones de compilar
#else Especifica alternativas cuando la prueba de #if falla
#elif Especifica alternativas cuando más de dos condiciones se necesitan
 Definiciones de macro
 Inclusiones de archivos
 Control de compilación
Ahora, vamos a presentar sólo las directivas del preprocesador utilizadas con
más frecuencia. Sin embargo, no es necesario saber todas ellas para
programar microcontroladores. Sólo tenga en cuenta que el preprocesador es
una herramienta muy poderosa para los programadores avanzados en C,
especialmente para el control de compilación.

DIRECTIVAS DEL PREPROCESADOR PARA DEFINIR MACROS

Por medio de los macros es posible definir las constantes y ejecutar funciones
básicas. Una sustitución de macro es un proceso en el que un identificador del
programa se sustituye por una cadena predefinida. El preprocesador sustituye
cada ocurrencia del identificador en el código fuente por una cadena. Después
de la sustitución, el código será compilado normalmente.
Esto significa que el código sustituido debe respetar la sintaxis del mikroC. La
acción se realiza por medio de la directiva '#define'.

#define PI 3.14159 // Sustitución simple, PI será sustituido por


// el valor 3.14159 en todas las partes del programa

También puede utilizar los parámetros para realizar substituciones más


complejas:

#define VOLUMEN (D,H) (((D/2)*(D/2)*PI))*H // Macro con parámetros

Entonces, en el código, la siguiente sentencia:

Tanque_1 = VOLUMEN (Diámetro,altura);

será sustituida por:

Tanque_1 = (((Diámetro/2)*(Diámetro/2)*PI)*altura;

Por medio de la directiva #undef es posible quitar una definición de nombre de


macro. Así se especifica que la substitución que se ha definido anteriormente
ya no va ocurrir en el siguiente código. Esto es útil cuando usted quiere
restringir la definición sólo a una parte particular del programa.

#undef TANQUE // Quitar la definición del macro VOLUMEN

INCLUSIÓN DE ARCHIVOS

La directiva de preprocesador #include copia un archivo específico en el código


fuente. El código incluido debe observar la sintaxis de C para ser compilado
correctamente.
Hay dos formas de escribir estas directivas. En el primer ejemplo, sólo el
nombre de archivo se especifica, así que el preprocesador lo buscará dentro
del archivo include. En el segundo ejemplo, se especifica la ruta entera, así que
el archivo estará directamente incluido (este método es más rápido).

#include <nombre_de_archivo> // Se especifica sólo el nombre del


archivo
#include "C:\Ruta\nombre_de_archivo.h" // Se especifica la localidad
// exacta del archivo

2.10 MIKROC PRO FOR PIC

Como ya hemos visto, hay varias divergencias entre los lenguajes mikroC y
ANSI C. En este capítulo vamos a presentar las características específicas del
mikroC con el propósito de facilitar la programación de los microcontroladores
PIC.

ACCESO A LOS REGISTROS DE FUNCIONES ESPECIALES (SFR)

Como todos los microcontroladores, los de familia PIC tienen los registros de
funciones especiales (SFR). Para programar un PIC, es necesario acceder a
estos registros (para leerlos o escribir en ellos). Al utilizar el compilador mikroC
PRO for PIC es posible de acceder a cualquier SFR del microcontrolador de
cualquier parte del código (los SFR se consideran como variables globales) sin
necesidad de declararlo anteriormente. Los registros de funciones especiales
se definen en un archivo externo e incluido dentro del compilador (archivo .def).
Este archivo contiene todos los SFR del microcontrolador PIC a programar.

TRISB = 0; // todos los pines del puerto PORTB se configuran como salidas
PORTB = 0; // todos los pines del PORTB se ponen a 0

ACCESO A LOS BITS INDIVIDUALES

El compilador mikroC PRO for PIC le permite acceder a los bits individuales de
variables de 8 bits por su nombre o su posición en byte:

INTCON.B0 = 0; // Poner a 0 el bit 0 del registro INTCON


ADCON0.F5 = 1; // Poner a 1 el bit 5 del registo ADCON0
INTCON.GIE = 0; // Poner a 0 el bit de interrupción global (GIE)

Para acceder a un bit individual, se puede utilizar '.FX' así como '.BX' (X es un
entero entre 0 y 7 que representa la posición de bit).

TIPO SBIT

Si quiere declarar una variable que corresponde a un bit de un SFR, hay que
utilizar el tipo sbit. Una variable de tipo sbit se comporta como un puntero y se
debe declarar como una variable global:

sbit Botón_PARADA at PORTA.B7; // Botón_PARADA está definido


...
void main() { // Cualquier modificación de Botón_PARADA afectará
a PORTA.B7
... // Cualquier modificación de PORTA.B7 afectará a
Botón_PARADA
}

En este ejemplo, El Botón_PARADA es una variable declarada por el usuario,


mientras que PORTA.B7 (bit 7 del puerto PORTA) será automáticamente
reconocido por el compilador.
TIPO BIT

El compilador mikroC PRO for PIC proporciona un tipo de datos bit que se
puede utilizar para declarar variables. No se puede utilizar en las listas de
argumentos, punteros y los valores devueltos de funciones. Además, no es
posible declarar e inicializar una variable de tipo bit en la misma línea. El
compilador determina el bit en uno de los registros disponibles para almacenar
las variables.

bit bf; // Variable de tipo bit válida


bit *ptr; // Varibale de tipo bit inválida.
// No hay punteros a una variable de tipo bit
bit bg = 0; // ERROR ; declaración con inicialización no está permitida
bit bg;
bg = 0; // Declaración e inicialización válidas

INSERTAR CÓDIGO ASM EN C

A veces el proceso de escribir un programa en C requiere las partes del código


escritas en ensamblador. Esto permite ejecutar las partes complicadas del
programa de una forma definida con precisión en un período de tiempo exacto.
Por ejemplo, cuando se necesita que los pulsos muy cortos (de unos
microsegundos) aparezcan periódicamente en un pin del microcontrolador. En
tales casos la solución más simple sería utilizar el código ensamblador en la
parte del programa que controla la duración de pulsos.
Una o más instrucciones en ensamblador están insertadas en el programa
escrito en C, utilizando el comando asm:

asm
{
instrucciones en ensamblador
...
}

Los códigos escritos en ensamblador pueden utilizar constantes y variables


anteriormente definidos en C. Por supuesto, como el programa entero está
escrito en C, sus reglas se aplican al declarar estas constantes y variables.

unsigned char maximum = 100; // Declarar variables: maximum = 100


asm
{ // Inicio del código ensamblador
MOVF maximum,W // W = maximum = 100
...
} // Final del código ensamblador

FUNCIÓN DE INTERRUPCIÓN

Una interrupción detiene la ejecución normal de un programa para ejecutar las


operaciones específicas. Una lista de sentencias a ejecutar debe estar escrita
dentro de una función particular denominada interrupt(). La sintaxis de una
interrupción en mikroC se parece a lo siguiente:

void interrupt() {
cnt++ ; // Al producirse una interrupción
// la cnt se incrementa en 1
PIR1.TMR1IF = 0; // Poner a 0 el bit TMR1IF
}

A diferencia de las funciones estándar, no es necesario declarar el prototipo de


la función interrupt(). Además, como la ejecución de esta función no forma
parte de la ejecución de programa regular, no se debe llamar de ninguna parte
de programa (se ejecutará automáticamente dependiendo de las condiciones
que el usuario ha definido en el programa). En el siguiente capítulo vamos a
dar una clara explicación de la ejecución y definición de subrutinas de
interrupción.
LIBRERÍAS

Usted probablemente ha notado que en los ejemplos anteriores hemos utilizado


algunas funciones como son 'Delay_ms', 'LCD_out', 'LCD_cmd' etc. Estas
funciones están definidas en las librerías contenidas en el compilador mikroC.
Una librería representa un código compilado, anteriormente escrito en mikroC,
que contiene un conjunto de variables y funciones. Cada librería tiene un
propósito específico. Por ejemplo, la librería LCD contiene funciones de
visualización de la pantalla LCD, mientras queC_math proporciona algunas
funciones matemáticas.
Antes de utilizar alguna de ellas en el programa, es necesario comunicárselo al
compilador al marcarlas en la lista de las librerías del compilador existentes. Si
el compilador encuentra una función desconocida durante la ejecución de
programa, primero va a buscar su declaración en las librerías marcadas.

Aparte de las librerías existentes, es posible crear las librerías y luego


utilizarlas en el programa. El procedimiento de cómo crear librerías se describe
en detalles en Help (Ayuda) del compilador.
El compilador mikroC incluye tres tipos de librerías:
- librerías ANSI C estándar:

L I B R AR Í A DESCRIPCIÓN
ANSI C Ctype
Utilizada principalmente para probar o para convertir los datos
Library
ANSI C Math Library Utilizada para las operaciones matemáticas de punto flotante
ANSI C Stdlib Library Contiene las funciones de librerías estándar
ANSI C String Utilizada para realizar las operaciones de cadenas y de manipula
Library memoria
- librerías misceláneas:

L I B R AR Í A DESCRIPCIÓN
Button Library Utilizada para desarrollar los proyectos
Conversion Library Utilizada para la conversión de tipos de datos
Sprint Library Utilizada para formatear los datos con facilidad
PrintOut Library Utilizada para formatear los datos e imprimirlos
Time Library Utilizada para cálculos de tiempo (formato UNIX time)
Trigonometry Library Utilizada para la implementación de funciones trigonométricas fundamen
Setjmp Library Utilizada para los saltos de programa
- librerías para el hardware:

L I B R AR Í A DESCRIPCIÓN
ADC Library Utilizada para el funcionamiento del convertidor A/D
CAN Library Utilizada para las operaciones con el módulo CAN
Utilizada para las operaciones con el módulo CAN externo (MC
CANSPI Library
MCP2510)
Utilizada para las operaciones con las tarjetas de memoria C
Compact Flash Library
Flash
EEPROM Library Utilizada para las operaciones con la memoria EEPROM incorpo
EthernetPIC18FxxJ60 Library Utilizada para las operaciones con el módulo Ethernet incorporad
Flash Memory Library Utilizada para las operaciones con la memoria Flash incorporada
Utilizada para las operaciones con el módulo LCD gráfico con re
Graphic Lcd Library
128x64
I2C Library Utilizada para las operaciones con el módulo de comunicación s
incorporado
Keypad Library Utilizada para las operaciones con el teclado (botones de presión
Lcd Library Utilizada para las operaciones con el LCD (de 2x16 caracteres)
Manchester Code Library Utilizada para la comunicación utilizando el código Manchester
Multi Media Card Library Utilizada para las operaciones con las tarjetas multimedia MMC f
Utilizada para las operaciones con los circuitos utiliza
One Wire Library
comunicación serial One Wire
Port Expander Library Utilizada para las operaciones con el extensor de puertos MCP23
PS/2 Library Utilizada para las operaciones con el teclado estándar PS/2
PWM Library Utilizada para las operaciones con el módulo PWM incorporado
Utilizada para las operaciones con los módulos utiliza
RS-485 Library
comunicación serial RS485
Software I2C Library Utilizada para simular la comunicación I2C con software
Software SPI Library Utilizada para simular la comunicación SPI con software
Software UART Library Utilizada para simular la comunicación UART con software
Sound Library Utilizada para generar las señales de audio
SPI Library Utilizada para las operaciones con el módulo SPI incorporado
Utilizada para la comunicación SPI con el módulo ETH
SPI Ethernet Library
(ENC28J60)
SPI Graphic Lcd Library Utilizada para la comunicación SPI de 4 bits con el LCD gráfico
Utilizada para la comunicación SPI de 4 bits con el LCD (d
SPI LCD Library
caracteres)
SPI Lcd8 Library Utilizada para la comunicación SPI de 8 bits con el LCD
SPI T6963C Graphic Lcd
Utilizada para la comunicación SPI con el LCD gráfico
Library
UART Library Utilizada para las operaciones con el módulo UART incorporado
USB Hid Library Utilizada para las operaciones con el módulo USB incorporado
2.11 PROGRAMAR LOS PIC UTILIZANDO MIKROC PRO FOR PIC

En las secciones anteriores hemos presentado el lenguaje mikroC,


especialmente diseñado para programar los PIC. Ahora, lo que hemos revisado
es suficiente para empezar a programar, es hora de presentar el software que
utilizará para desarrollar y editar los proyectos. Este software se le denomina
Entorno de desarrollo integrado (Integrated Developement Environment - IDE)
e incluye todas las herramientas necesarias para desarrollar los proyectos
(editor, depurador etc.). Por extensión, IDE es a veces llamado compilador. En
esta sección le enseñaremos lo básico que debe saber para empezar a
desarrollar su primer proyecto en mikroC utilizando el IDE del compilador
mikroC PRO for PIC.

Aparte de todas las características comunes de cualquier IDE, mikroC PRO for
PIC contiene las informaciones de arquitectura de los microcontroladores PIC
(registros, módulos de memoria, funcionamiento de circuitos particulares etc.)
para compilar y generar un archivo legible por un microcontrolador PIC.
Además, incluye las herramientas específicas para programar los
microcontroladores PIC.

El proceso de crear y ejecutar un proyecto contiene los siguientes pasos:

1. Crear un proyecto (nombre de proyecto, configuración de proyecto,


dependencias entre archivos)
2. Editar un programa
3. Compilar el programa y corrección de errores
4. Depurar (ejecutar el programa paso a paso para asegurarse de que se
ejecutan las operaciones deseadas).
5. Programar un microcontrolador (cargar el archivo .hex generado por el
compilador en el microcontrolador utilizando el programador PICflash).
INSTALAR MIKROC PRO FOR PIC

Antes que nada, usted debe instalar el compilador (con su IDE) en la PC. La
instalación del mikroC PRO for PIC es similar a la instalación de cualquier
programa en Windows. Todo el procedimiento se lleva a cabo por medio de los
wizards (asistentes de instalación):
Basta con seguir las instrucciones y pulsar sobre Next, OK, Next, Next... En
general, es el mismo procedimiento menos la última opción: 'Do you want to
install PICFLASH v7.11 programmer?'. ¿Para qué sirve este software? De eso
vamos a hablar más tarde. Por ahora, basta con saber que es un software
autónomo utilizado para cargar el programa en el microcontrolador.

Una vez más: Next, OK, Next, Next...

Una vez completada la instalación del PICflash, el sistema operativo le


preguntará a instalar otro programa similar, un software para programar un
grupo especial de los microcontroladores PIC que funcionan en modo de bajo
consumo (3.3 V). Salte este paso...

El último paso - ¡la instalación del controlador (driver)!

Pulse sobre Yes.

Un controlador es un programa que permite al sistema operativo comunicar con


un periférico. En este caso, este dispositivo es el programador (hardware) del
sistema de desarrollo.

El controlador a instalar depende del sistema operativo utilizado. Seleccione el


controlador correspondiente al SO (sistema operativo) utilizado (por medio de
abrir la carpeta correspondiente) e inicie la instalación. Otra vez, Next, OK,
Next, Next... Bueno, ¡todo está instalado para iniciar a programar!
CARACTERÍSTICAS PRINCIPALES DEL MIKROC PRO FOR PIC

Al iniciar el IDE del compilador mikroC PRO for PIC por primera vez, aparecerá
una ventana como se muestra a continuación:

Desgraciadamente, una descripción detallada de todas las opciones


disponibles de este IDE nos tomaría mucho tiempo. Por eso vamos a describir
sólo lo más importante del compilador mikroC PRO for PIC. De todos modos,
para obtener más informacion presione el botón de Ayuda (Help) [F1].

MANEJAR LOS PROYECTOS

Antes de empezar a escribir el código, usted debe crear un proyecto. Un


programa escrito en el compilador mikroC PRO for PIC no es un archivo fuente
autónomo, sino que forma parte de un proyecto que incluye un código hex, un
código ensamblador, cabecera y otros archivos. Algunos de ellos se requieren
para compilar el programa, mientras que otros se crean durante el proceso de
compilación. Un archivo con extensión .mcppi le permite abrir cualquiera de
estos proyectos.
Para crear un proyecto, basta con seleccionar la opción Project/New Project, y
un wizard aparecerá automáticamente. ¿Qué hacer entonces? Siga las
instrucciones...
PROJECT MANAGER (ADMINISTRADOR DEL PROYECTO)

Una vez creado el proyecto, es posible manejar todos los archivos que contiene
al utilizar la ventana Project Manager. Basta con pulsar con el botón derecho
del ratón sobre una carpeta y seleccionar la opción que necesita para su
proyecto.

PROJECT SETTINGS (CONFIGURACIÓN DE PROYECTOS)

Al compilar un proyecto, el compilador genera el archivo .hex que se cargará en


el microcontrolador. Estos archivos serán diferentes lo que depende del tipo del
microcontrolador así como del propósito de la compilación. Por esta razón es
necesario ajustar algunos parámetros de proyectos utilizando la
ventana Project Settings.
Device (dispositivo):
Al seleccionar el tipo de microcontrolador a utilizar permite al compilador
extraer el archivo de definición (archivo .def) asociado. El archivo de definición
de un microcontrolador contiene las informaciones específicas de sus registros
SFR, de sus direcciones de memoria y algunas variables de programación
específicas a ese tipo del microcontrolador. Es obligatorio crear un archivo .hex
compatible.

Oscillator (oscilador):
Se debe especificar la velocidad de operación del microcontrolador. Por
supuesto, este valor depende del oscilador utilizado. El compilador la necesita
para compilar rutinas, lo que requiere información del tiempo (por ejemplo, la
función Delay_ms). Más tarde, el programador necesitará esta información
también. La velocidad de operación se configura de modo que permita al
oscilador interno del microcontrolador funcionar a una frecuencia seleccionada.

Build/Debugger Type:
Todo el proceso de compilar (building) está compuesto por análisis sintáctico
(parsing), compilar, enlazar (linking) y generar los archivos .hex. El tipo de
compilación le permite ajustar el modo de compilación. Dependiendo del modo
seleccionado, difieren los archivos generados a cargar en el microcontrolador.

Release: Al elegir esta opción , el compilador no puede afectar más a la


ejecución de programa después de la compilación. El programa a cargar en el
microcontrolador no será modificado de ninguna manera.
ICD debug: Al elegir esta opción, una vez completado el proceso de la
compilación y cargado el programa en la memoria del microcontrolador, el
compilador se queda conectado al microcontrolador por medio del cable USB y
el programador, y todavía puede afectar a su funcionamiento. El archivo .hex
generado contiene los datos adicionales que permiten el funcionamiento del
depurador. Una herramienta denominada mikroICD (Depurador en circuito - In
Circuit Debugger) permite ejecutar el programa paso a paso y proporcionar un
acceso al contenido actual de todos los registros de un microcontrolador real.
El simulador no utiliza los dispositivos reales para simular el funcionamiento del
microcontrolador, así que algunas operaciones no pueden ser reproducidas
(por ejemplo, interrupción). De todos modos, resulta más rápido depurar un
programa por medio de un simulador. Además, no se requiere ningún
dispositivo destino.

Note que es posible modificar cualquier configuración en cualquier momento


mientras se edita el programa. No se olvide de recompilar y reprogramar su
dispositivo después de modificar una configuración.

LIBRARY MANAGING (EDITOR DE LIBRERÍAS)

El compilador tiene que conocer todas las dependencias de su archivo fuente


en mikroC para compilarlo apropiadamente. Por ejemplo, si las librerías forman
parte de su proyecto, debe especificar cuáles de ellas se utilizan.

Las librerías contienen un gran número de funciones listas para ser utilizadas.
Las librerías en mikroC proporcionan muchas facilidades para escribir
programas para los microcontroladores PIC. Abra la ventana Library Manager,
y marque las que quiere utilizar en el programa. Al marcar una librería, se
añade automáticamente al proyecto y se enlaza durante el proceso de la
compilación. Así, no necesita incluir las librerías manualmente en sus archivos
del código fuente por medio de la directiva del preprocesador #include.
Por ejemplo, si su programa utiliza un LCD no hace falta escribir nuevas
funciones ya que al seleccionar la librería Lcd, usted podrá utilizar funciones
listas para ser utilizadas de la librería LCD (Lcd_Cmd, LCD_Init...) en su
programa. Si esta librería no está seleccionada en la ventana Library Manager,
cada vez que intente utilizar una función de la librería LCD, el compilador le
informará de un error. Una descripción de cada librería está disponible al pulsar
con el botón derecho del ratón sobre su nombre y seleccionar la opción Help.

EDITAR Y COMPILAR PROGRAMAS


CODE EDITOR (EDITOR DE CÓDIGO)

El proceso de editar programas se debe realizar dentro de la ventana principal


del IDE denominada Code Editor. Al escribir el programa no se olvide de los
comentarios. Los comentarios son muy importantes para depurar y mejorar el
programa. Además, aunque el compilador no tenga las restricciones de
formateo, siempre debe seguir a las mismas reglas de editar (como en los
ejemplos proporcionados en este libro). Como no hay limitaciones de tamaño,
no vacile en utilizar los espacios en blanco para hacer su código más legible.
Al escribir un programa, no espere que termine la redacción del programa para
compilarlo. Compile su código de forma regular con el propósito de corregir
cuánto más errores de sintaxis. Asimismo usted puede compilar su programa
cada vez que se complete la redacción de una nueva función así como probar
su comportamiento al utilizar modo de depuración (ver la próxima sección). De
este modo, resulta más fácil solucionar los errores de programa para no “tomar
un camino erróneo” en redactar su programa. De lo contrario, usted tendrá que
editar el programa entero.

CODE EXPLORER (EXPLORADOR DEL CÓDIGO)

La ventana Code Explorer le permite localizar funciones y procedimientos


dentro de los programas largos. Por ejemplo, si usted busca una función
utilizada en el programa, basta con pulsar dos veces sobre su nombre en esta
ventana, y el cursor estará automáticamente posicionado en la línea apropiada
en el programa.

COMPILAR Y SOLUCIONAR LOS ERRORES

Para compilar su código, pulse sobre la opción Build en el menú Project. En


realidad, el proyecto entero se ha compilado, y si la compilación se ha realizado
con éxito, se generarán los archivos de salida (asm, .hex etc.). Una compilación
se ha realizado con éxito si no se ha encontrado ningún error. Durante el
proceso de compilación se generan muchos mensajes que se visualizan en la
ventana Messages. Estos mensajes consisten en información, advertencia y
errores. Cada error encontrado se asocia con su línea de programa y su
descripción.
Como un error en su código puede generar mucho más errores, simplemente
debe intentar solucionar el primer error en la lista y después recompile su
programa. En otras palabras, es recomendable solucionar los errores uno a
uno.

En el ejemplo anterior hay dos errores y una advertencia: faltan un punto y


coma y una declaración de variable La advertencia le informa que falta el tipo
del valor devuelto de la función main.
La compilación le permite corregir su programa por medio de solucionar todos
los errores en mikroC. Cuando todos los errores se solucionen, su programa
está listo para ser cargado en el microcontrolador. De todas formas, su tarea
todavía no está terminada, porque aún no sabe si su programa se comporta
como se esperaba o no.

DEPURAR EL PROGRAMA

La depuración es un paso muy importante ya que permite probar el programa


después de una compilación realizada con éxito, o solucionar los errores
descubiertos mientras se ejecuta el programa. Como ya hemos visto, hay dos
modos de depurar: un depurador software que simula el funcionamiento del
microcontrolador (modo por defecto) y depurador hardware (mikroICD) que lee
directamente el contenido de la memoria del microcontrolador. El procedimiento
de depuración es el mismo sin reparar en el modo elegido. En caso de elegir la
opción ICD debug, hay que cargar el programa en el microcontrolador antes de
depurarlo.
Para iniciar la depuración, pulse sobre la opción Start debugger del menú Run.
El editor del código será ligeramente modificado automáticamente y aparecerá
una ventana denominada Watch Values. El principio de depuración se basa en
ejecutar el programa paso a paso y monitorear el contenido de los registros y
los valores de las variables. De este modo, es posible comprobar el resultado
de un cálculo y ver si algo inesperado ha ocurrido. Al ejecutar el programa paso
a paso, podrá localizar los problemas con facilidad.
Durante una depuración el programa será modificado, por lo que usted siempre
debe recompilar el programa después de cada corrección, y reiniciar el
depurador para comprobar qué ha sido modificado.

COMANDOS DEL DEPURADOR

Hay varios comandos disponibles para depurar el código:

 Step Into - Ejecuta una sola instrucción. Cuando la instrucción es una


llamada a una rutina, el depurador hará un salto a la rutina y se detendrá
después de ejecutar la primera instrucción dentro de la rutina.
 Step Over - Se ejecuta una sola instrucción. Cuando la instrucción es
una llamada a una rutina, el depurador no hará un salto a la rutina, sino
que se ejecutará toda la rutina. El depurador se detiene a la primera
instrucción después de la llamada a la rutina.
 Run To Cursor - El programa se ejecuta hasta la línea en la que se
encuentre el cursor.
 Step out - Se ejecutan las demás instrucciones dentro de la rutina. El
depurador se detiene inmediatamente al salir de la rutina.

PUNTOS DE RUPTURA (BREAKPOINTS)

Los puntos de ruptura hacen el proceso de depurar los programas de una


manera más eficiente, puesto que permiten ejecutar el programa a toda
velocidad y detenerlo automáticamente en una línea específica (punto de
ruptura). Eso resulta muy útil, permitiéndole comprobar sólo las partes críticas
del programa y no perder el tiempo probando todo el programa línea a línea.
Para añadir o quitar un punto de ruptura basta con pulsar sobre la línea
apropiada en el lado izquierdo del editor del código, o presionar [F5]. Una
pequeña ventana denominada Breakpoints muestra dónde están los puntos de
ruptura. Note que las líneas designadas como puntos de ruptura están
marcadas en rojo.

La línea que se está ejecutando actualmente está marcada en azul. Es posible


leer el contenido de registros y variables seleccionados en la ventana Watch
Values en cualquier momento. Para ejecutar la parte de programa desde la
línea en la que está el cursor hasta el punto de ruptura, utilice el comando
Run/Pause Debugger.
VENTANA WATCH VALUES

El depurador software y hardware tienen la misma función de monitorear el


estado de los registros durante la ejecución del programa. La diferencia es que
el depurador software simula ejecución de programa en una PC, mientras que
el depurador ICD (depurador hardware) utiliza un microcontrolador real.
Cualquier cambio de estado lógico de los pines se indica en el registro (puerto)
apropiado. Como la ventana Watch Values permite monitorear el estado de
todos los registros, resulta fácil comprobar si un pin está a cero o a uno. La
última modificación está marcada en rojo en la ventana Watch Values. Esto le
permite localizar la modificación en la lista de variables y registros durante el
proceso de la depuración.
Para visualizar esta ventana es necesario seleccionar la opción View/Debug
Windows/Watch Values. Entonces usted puede hacer una lista de
registros/variables que quiere monitorear y la manera de visualizarlos.
STOPWATCH (CRONÓMETRO)

Si quiere saber cuánto tiempo tarda un microcontrolador en ejecutar una parte


del programa, seleccione la opción Run/View Stopwatch. Aparecerá una
ventana como se muestra en la figura a la derecha. ¿Cómo funciona un
cronómetro? Eso es pan comido... El tiempo que tarda un comando (step into,
step over, run/pause etc.) en ejecutarse por el depurador se mide
automáticamente y se visualiza en la ventana Stopwatch. Por ejemplo, se mide
tiempo para ejecutar un programa, tiempo para ejecutar el último paso etc.

PROGRAMAR EL MICROCONTROLADOR

Si ha solucionado todos los errores en su código y cree que su programa está


listo para ser utilizado, el siguiente paso es cargarlo en el microcontrolador. El
programador PICflash se utiliza para este propósito. Es una herramienta
diseñada para programar todos los tipos de microcontroladores PIC. Está
compuesto por dos partes:
 La parte hardware se utiliza para introducir un código hexadecimal (el
programa a ser cargado en el microcontrolador) y para programar el
microcontrolador por medio de niveles de voltaje específicos. Durante el
proceso de la programación, un nuevo programa se escribe en la
memoria flash del microcontrolador, mientras que el programa anterior
se borra automáticamente.
 La parte de software se encarga de enviar el programa (archivo .hex ) a
la parte hardware del programador por medio de un cable USB. A la
interfaz de usuario de este software se le puede acceder desde IDE al
pulsar sobre la opción mE_Programmer del menú Tools o al pulsar
[F11]. Por consiguiente, es posible modificar algunas configuraciones del
programador y controlar el funcionamiento de la parte hardware (Cargar,
Escribir, Verificar...).

Se puede reprogramar el microcontrolador tantas veces como se necesite.

OTRAS HERRAMIENTAS DEL COMPILADOR

El compilador mikroC PRO for PIC proporciona herramientas que en gran


medida simplifican el proceso de escribir el programa. Todas estas
herramientas se encuentran en el menú Tools. En la siguiente sección vamos a
darle una breve descripción de todas ellas.

TERMINAL USART

El terminal USART representa una sustitución para la estándar Windows Hyper


Terminal. Se puede utilizar para controlar el funcionamiento del
microcontrolador que utiliza la comunicación USART. Tales microcontroladores
están incorporados en un dispositivo destino y conectados al conector RS232
de la PC por medio de un cable serial.
La ventana USART terminal dispone de opciones para configurar la
comunicación serial y visualizar los datos enviados/ recibidos.

EDITOR EEPROM

Al seleccionar la opción EEPROM Editor del menú Tools, aparecerá una


ventana como se muestra en la siguiente figura. Así es cómo funciona la
memoria EEPROM del microcontrolador. Si quiere cambiar de su contenido
después de cargar el programa en el microcontrolador, ésta es la forma
correcta de hacerlo. El nuevo contenido es un dato de un tipo específico (char,
int o double), primero debe seleccionarlo, introducir el valor en el campo Edit
Value y pulsar sobre Edit. Luego, pulse sobre el botón Save para guardarlo
como un documento con extensión .hex. Si la opción Use EEPROM in Project
está activa, los datos se cargarán automáticamente en el microcontrolador
durante el proceso de la programación.
VENTANA ASCII CHART

Si necesita representar numéricamente un carácter ASCII, seleccione la opción


ASCII chart del menú Tools. Aparecerá una tabla, como se muestra en la figura
que está a continuación. Usted probablemente sabe que cada tecla de teclado
está asociada con un código (código ASCII). Como se puede ver, los
caracteres que representan los números tienen valores diferentes. Por esta
razón, la instrucción de programa para visualizar el número 7 en un LCD no
visualizará este número, sino el equivalente a la instrucción BEL. Si envía el
mismo número en forma de un carácter a un LCD, obtendrá el resultado
esperado - número 7. Por consiguiente, si quiere visualizar un número sin
convertirlo en un carácter apropiado, es necesario añadir el número 48 a cada
dígito en el que consiste el número a visualizar.
EDITOR DE SIETE SEGMENTOS

Un editor de siete segmentos le permite determinar con facilidad el número a


poner en un puerto de salida con el propósito de visualizar un símbolo
deseado. Por supuesto, se da por entendido que los pines del puerto deben
estar conectados a los segmentos del visualizador de manera apropiada. Basta
con colocar el cursor en cualquier segmento del visualizador y pulsar sobre él.
Se visualizará inmediatamente el número a introducir en el programa.
LCD CUSTOM CHARACTER (CARACTERES LCD DEFINIDOS POR EL
USUARIO)

Además de los caracteres estándar, el microcontrolador también puede


visualizar los caracteres creados por el programador. Al seleccionar la
herramienta LCD custom character, se evitará un pesado trabajo de crear
funciones para enviar un código apropiado a un visualizador. Para crear un
símbolo, pulse sobre los cuadros pequeños en la ventana LCD custom
character, luego seleccione la posición y la fila y pulse sobre el botón
GENERATE. El código apropiado aparece en otra ventana. No es necesita
pulsar más. Copy to Clipboard (copiar al portapapeles) - Paste (pegar)...

GENERADOR DE MAPA DE BITS PARA UN LCD GRÁFICO

El generador de mapa de bits para un LCD gráfico es una herramienta


insustituible en caso de que el programa que escribe utilice el visualizador LCD
(GLCD). Esta herramienta le permite visualizar un mapa de bits con facilidad.
Seleccione la opción Tools/Glcd Bitmap Editor aparecerá la ventana apropiada.
Para utilizarlo, seleccione el tipo de visualizador a utilizar y cargue un mapa de
bits. El mapa de bits debe ser monocromático y tener la resolución apropiada
del visualizador (128 x 64 píxeles en este ejemplo). El procedimiento a seguir
es igual que en el ejemplo anterior: Copy to Clipboard...
Un código generado que utiliza herramientas para controlar los visualizadores
LCD y GLCD contiene funciones de la librería Lcd. Si las utiliza en el programa,
no se olvide de marcar la caja de chequeo junto a esta librería en la ventana
Library Manager. Así el compilador será capaz de reconocer estas funciones
correctamente.
……………………………………………………………………………………………
……………………………………………….

Capitulo 4: Ejemplos

El propósito de este capítulo es de proporcionar la información básica que


necesita saber para ser capaz de utilizar microcontroladores con éxito en la
práctica. Por eso, este capítulo no contiene ningún programa muy elaborado,
tampoco dispone de un esquema de dispositivo con soluciones extraordinarias.
Por el contrario, los siguientes ejemplos son la mejor prueba de que escribir un
programa no es un privilegio ni cosa de talento, sino una habilidad de “poner
las piezas juntas del rompecabezas” al utilizar directivas. Tenga la seguridad de
que el diseño y el desarrollo de los dispositivos generalmente siguen al método
“probar-corregir-repetir”. Por supuesto, cuánto más ahonde sobre el tema, más
se complica, ya que tanto los niños como los arquitectos de primera línea,
ponen las piezas juntas del rompecabezas.
 4.1 CONEXIÓN BÁSICA
 4.2 COMPONENTES ADICIONALES
 4.3 EJEMPLO 1 - Escribir cabecera, configurar pines de E/S, utilizar la
función Delay y el operador Switch
 4.4 EJEMPLO 2 - Utilizar instrucciones en ensamblador y el oscilador
interno LFINTOSC...
 4.5 EJEMPLO 3 - Timer0 como un contador, declarar variables nuevas,
constantes de enumeración, utilizar relés...
 4.6 EJEMPLO 4 - Utilizar los temporizadores Timer0, Timer1 y Timer2.
 4.7 EJEMPLO 5 - Utilizar el temporizador perro - guardián
 4.8 EJEMPLO 6 - Módulo CCP1 como generador de señal PWM
 4.9 EJEMPLO 7 - Utilizar el convertidor A/D
 4.10 EJEMPLO 8 - Utilizar memoria EEPROM
 4.11 EJEMPLO 9 - Contador de dos dígitos LED, multiplexión
 4.12 EJEMPLO 10 - Utilizar el visualizador LCD
 4.13 EJEMPLO 11 - Comunicación serial RS-232
 4.14 EJEMPLO 12 - Medición de temperatura por medio del sensor
DS1820. Uso del protocolo '1-wire'...
 4.15 EJEMPLO 13 - Generación de sonido, librería de sonido...
 4.16 EJEMPLO 14 - Utilizar el visualizador LCD gráfico
 4.17 EJEMPLO 15 - Utilizar el panel táctil...
4.1 CONEXIÓN BÁSICA

Para que un microcontrolador funcione apropiadamente es necesario


proporcionar lo siguiente:

 Alimentación;
 Señal de reinicio; y
 Señal de reloj.
Como se muestra en la figura anterior, se trata de circuitos simples, pero no
tiene que ser siempre así. Si el dispositivo destino se utiliza para controlar las
máquinas caras o para mantener funciones vitales, todo se vuelve mucho más
complicado.

ALIMENTACIÓN

Aunque el PIC16F887 es capaz de funcionar a diferentes voltajes de


alimentación, no es recomendable probar la ley de Murphy. Lo más adecuado
es proporcionar un voltaje de alimentación de 5V DC. Este circuito, mostrado
en la página anterior, utiliza un regulador de voltaje positivo de tres terminales
LM7805. Es un regulador integrado y barato que proporciona una estabilidad
de voltaje de alta calidad y suficiente corriente para habilitar el funcionamiento
apropiado del controlador y de los periféricos (aquí suficiente significa una
corriente de 1A).

SEÑAL DE REINICIO

Para que un microcontrolador pueda funcionar apropiadamente, un uno lógico


(VCC) se debe colocar en el pin de reinicio. El botón de presión que conecta el
pin MCLR a GND no es necesario. Sin embargo, este botón casi siempre está
proporcionado ya que habilita al microcontrolador volver al modo normal de
funcionamiento en caso de que algo salga mal. Al pulsar sobre el botón
RESET, el pin MCLR se lleva un voltaje de 0V, el microcontrolador se reinicia y
la ejecución de programa comienza desde el principio. Una resistencia de 10k
se utiliza para impedir un corto circuito a tierra al presionar este botón.

SEÑAL DE RELOJ

A pesar de tener un oscilador incorporado, el microcontrolador no puede


funcionar sin componentes externos que estabilizan su funcionamiento y
determinan su frecuencia (velocidad de operación del microcontrolador).
Dependiendo de los elementos utilizados así como de las frecuencias el
oscilador puede funcionar en cuatro modos diferentes:

 LP - Cristal de bajo consumo;


 XT - Cristal / Resonador;
 HS - Cristal/Resonador de alta velocidad; y
 RC - Resistencia / Condensador.

¿Por qué son estos modos importantes? Como es casi imposible construir un
oscilador estable que funcione a un amplio rango de frecuencias, el
microcontrolador tiene que “saber” a qué cristal está conectado, para poder
ajustar el funcionamiento de sus componentes internos. Ésta es la razón por la
que todos los programas utilizados para escribir un programa en el chip
contienen una opción para seleccionar el modo de oscilador. Vea la figura de la
izquierda.

Cristal de cuarzo
Al utilizar el cristal de cuarzo para estabilizar la frecuencia, un oscilador
incorporado funciona a una frecuencia determinada, y no es afectada por los
cambios de temperatura y de voltaje de alimentación. Esta frecuencia se
etiqueta normalmente en el encapsulado del cristal. Aparte del cristal, los
condensadores C1 y C2 deben estar conectados como se muestra en el
siguiente esquema. Su capacitancia no es de gran importancia. Por eso, los
valores proporcionados en la siguiente tabla se deben tomar como
recomendación y no como regla estricta.

Resonador cerámico

Un resonador cerámico es más barato y muy similar a un cuarzo por la función


y el modo de funcionamiento. Por esto, los esquemas que muestran su
conexión al microcontrolador son idénticos. No obstante, los valores de los
condensadores difieren un poco debido a las diferentes características
eléctricas. Refiérase a la tabla que está a continuación.

Estos resonadores se conectan normalmente a los osciladores en caso de que


no sea necesario proporcionar una frecuencia extremadamente precisa.

Oscilador RC
Si la frecuencia de operación no es de importancia, entonces no es necesario
utilizar los componentes caros y adicionales para la estabilización. En vez de
eso, basta con utilizar una simple red RC, mostrada en la siguiente figura.
Como aquí es utilizada sólo la entrada del oscilador local, la señal de reloj con
la frecuencia Fosc/4 aparecerá en el pin OSC2. Ésta es la frecuencia de
operación del microcontrolador, o sea la velocidad de ejecución de
instrucciones.

Oscilador externo

Si se requiere sincronizar el funcionamiento de varios microcontroladores o si


por alguna razón no es posible utilizar ninguno de los esquemas anteriores,
una señal de reloj se puede generar por un oscilador externo. Refiérase a la
siguiente figura.

Apesar del hecho de que el microcontrolador es un producto de la tecnología


moderna, no es tan útil sin estar conectado a los componentes adicionales.
Dicho de otra manera, el voltaje llevado a los pines del microcontrolador no
sirve para nada si no se utiliza para llevar a cabo ciertas operaciones como son
encender/apagar, desplazar, visualizar etc.
4.2 COMPONENTES ADICIONALES

Esta parte trata los componentes adicionales utilizados con más frecuencia en
la práctica, tales como resistencias, transistores, diodos LED, visualizadores
LED, visualizadores LCD y los circuitos de comunicación RS-232.

INTERRUPTORES Y BOTONES DE PRESIÓN

Los interruptores y los botones de presión son los dispositivos simples para
proporcionar la forma más simple de detectar la aparición de voltaje en un pin
de entrada del microcontrolador. No obstante, no es tan simple como parece...
Es por un rebote de contacto. El rebote de contacto es un problema común en
los interruptores mecánicos.

Al tocarse los contactos, se produce un rebote por su inercia y elasticidad. Por


consiguiente, la corriente eléctrica es rápidamente pulsada en lugar de tener
una clara transición de cero a la corriente máxima. Por lo general, esto ocurre
debido a las vibraciones, los desniveles suaves y la suciedad entre los
contactos. Este efecto no se percibe normalmente al utilizar estos componentes
en la vida cotidiana porque el rebote ocurre demasiado rápido para afectar a la
mayoría de los dispositivos eléctricos. Sin embargo, pueden surgir problemas
en algunos circuitos lógicos que responden lo suficientemente rápido de
manera que malinterpreten los pulsos producidos al tocarse los contactos como
un flujo de datos. De todos modos, el proceso entero no dura mucho (unos
pocos micro - o milisegundos), pero dura lo suficiente para que lo detecte el
microcontrolador. Al utilizar sólo un botón de presión como una fuente de señal
de contador, en casi 100% de los casos ocurren los errores.
El problema se puede resolver con facilidad al conectar un simple circuito RC
para suprimir rápidos cambios de voltaje. Como el período del rebote no está
definido, los valores de los componentes no están precisamente determinados.
En la mayoría de los casos es recomendable utilizar los valores que se
muestran en la siguiente figura.

Si se necesita una estabilidad completa, entonces hay que tomar medidas


radicales. La salida del circuito, mostrado en la siguiente figura (biestable RS,
también llamado flip flop RS), cambiará de estado lógico después de detectar el
primer pulso producido por un rebote de contacto. Esta solución es más cara
(interruptor SPDT), pero el problema es resuelto.

Aparte de estas soluciones de hardware, hay también una simple solución de


software. Mientras el programa prueba el estado de circuito lógico de un pin de
entrada, si detecta un cambio, hay que probarlo una vez más después de un
cierto tiempo de retardo. Si el programa confirma el cambio, esto significa que
un interruptor/botón de presión ha cambiado de posición. Las ventajas de esta
solución son obvias: es gratuita, se borran los efectos del rebote de contacto y
se puede aplicar a los contactos de una calidad más baja también.

RELÉ
Un relé es un interruptor eléctrico que se abre y se cierra bajo el control de otro
circuito electrónico. Por eso está conectado a los pines de salida del
microcontrolador y utilizado para encender/apagar los dispositivos de alto
consumo tales como: motores, transformadores, calefactores, bombillas etc.
Estos dispositivos se colocan casi siempre lejos de los componentes sensibles
de la placa. Hay varios tipos de relés, pero todos funcionan de la misma
manera. Al fluir la corriente por la bobina, el relé funciona por medio de un
electromagneto, abriendo y cerrando uno o más conjunto de contactos. Similar
a los optoacopladores no hay conexión galvánica (contacto eléctrico) entre los
circuitos de entrada y salida. Los relés requieren con frecuencia tanto un voltaje
más alto y una corriente más alta para empezar a funcionar. También hay relés
miniatura que se pueden poner en marcha por una corriente baja obtenida
directamente de un pin del microcontrolador.

La figura que sigue muestra la solución utilizada con más frecuencia.


Para prevenir la aparición de un alto voltaje de autoinducción, causada por una
parada repentina del flujo de corriente por la bobina, un diodo polarizado
invertido se conecta en paralelo con la bobina. El propósito de este diodo es de
“cortar” este pico de voltaje.

DIODOS LED

Probablemente sepa todo lo que necesita saber sobre los diodos LED, pero
también debe pensar en los jóvenes... A ver, ¿cómo destruir un LED?
Bueno...muy fácil.

Quemar con rapidez

Como cualquier otro diodo, los LEDs tienen dos puntas - un ánodo y un cátodo.
Conecte un diodo apropiadamente a la fuente de alimentación y va a emitir luz
sin ningún problema. Ponga al diodo al revés y conéctelo a la misma fuente de
alimentación (aunque sea por un momento). No emitirá luz - ¡nunca más!

Quemar lentamente

Hay un límite de corriente nominal, o sea, límite de corriente máxima


especificada para cada LED que no se deberá exceder. Si eso sucede, el diodo
emitirá luz más intensiva, pero sólo por un período corto de tiempo.

Algo para recordar

De manera similar, todo lo que tiene que hacer es elegir una resistencia para
limitar la corriente mostrada a continuación. Dependiendo de voltaje de
alimentación, los efectos pueden ser espectaculares.

VISUALIZADOR LED

Básicamente, un visualizador LED no es nada más que varios diodos LED


moldeados en la misma caja plástica. Hay varios tipos de los visualizadores y
algunos de ellos están compuestos por varias docenas de diodos incorporados
que pueden visualizar diferentes símbolos. No obstante, el visualizador
utilizado con más frecuencia es el visualizador de 7 segmentos. Está
compuesto por 8 LEDs. Los siete segmentos de un dígito están organizados en
forma de un rectángulo para visualizar los símbolos, mientras que el segmento
adicional se utiliza para el propósito de visualizar los puntos decimales. Para
simplificar la conexión, los ánodos y los cátodos de todos los diodos se
conectan al pin común así que tenemos visualizadores de ánodo común y
visualizadores de cátodo común, respectivamente. Los segmentos están
etiquetados con letras de a a g y dp, como se muestra en la siguiente figura. Al
conectarlos, cada diodo LED se trata por separado, lo que significa que cada
uno dispone de su propia resistencia para limitar la corriente.

Aquí le presentamos unas cosas importantes a las que debe prestar atención al
comprar un visualizador LED:

 Como hemos mencionado, dependiendo de si ánodos o cátodos están


conectados al pin común, tenemos visualizadores de ánodo común y
visualizadores de cátodo común. Visto de afuera, parece que no hay
ninguna diferencia entre estos visualizadores, pues se le recomienda
comprobar cuál se va a utilizar antes de instalarlo.
 Cada pin del microcontrolador tiene un límite de corriente máxima que
puede recibir o dar. Por eso, si varios visualizadores están conectados al
microcontrolador, es recomendable utilizar así llamados LEDs de Bajo
consumo que utilizan solamente 2mA para su funcionamiento.
 Los segmentos del visualizador están normalmente etiquetados con
letras de a a g, pero no hay ninguna regla estrictaa cuáles pines del
visualizador estarán conectados. Por eso es muy importante
comprobarlo antes de empezar a escribir un programa o diseñar un
dispositivo.

Los visualizadores conectados al microcontrolador normalmente ocupan un


gran número de los pines de E/S valiosos, lo que puede ser un problema sobre
todo cuando se necesita visualizar los números compuestos por varios dígitos.
El problema se vuelve más obvio si, por ejemplo, se necesita visualizar dos
números de seis dígitos (un simple cálculo muestra que en este caso se
necesitan 96 pines de salida). La solución de este problema es denominada
multiplexión.

Aquí es cómo se ha hecho una ilusión óptica basada en el mismo principio de


funcionamiento como una cámara de película. Un sólo dígito está activo a la
vez, pero se tiene la impresión de que todos los dígitos de un número están
simultáneamente activos por cambiar tan rápidamente de las condiciones de
encendido/apagado.

Veamos la figura anterior. Primero se aplica un byte que representa unidades al


puerto PORT2 del microcontrolador y se activa el transistor T1 a la vez.
Después de poco tiempo, el transistor T1 se apaga, un byte que representa
decenas se aplica al puerto PORT2 y el transistor T2 se activa. Este proceso se
está repitiendo cíclicamente a alta velocidad en todos los dígitos y transistores
correspondientes.

Lo decepcionante es que el microcontrolador es sólo un tipo de computadora


miniatura diseñada para interpretar el lenguaje de ceros y unos, lo que se pone
de manifiesto al visualizar cualquier dígito. Concretamente, el microcontrolador
no conoce cómo son unidades, decenas, centenas, ni diez dígitos a los que
estamos acostumbrados. Por esta razón, cada número a visualizar debe pasar
por el siguiente procedimiento:

Antes que nada, un número de varios dígitos debe ser dividido en unidades,
centenas etc. en una subrutina específica. Luego, cada de estos dígitos se
debe almacenar en los bytes particulares. Los dígitos se hacen reconocibles al
realizar "enmascaramiento". En otras palabras, el formato binario de cada
dígito se sustituye por una combinación diferente de los bits por medio de una
subrutina simple. Por ejemplo, el dígito 8 (0000 1000) se sustituye por el
número binario 0111 1111 para activar todos los LEDs que visualizan el
número 8. El único diodo que queda inactivo aquí está reservado para el punto
decimal.

Si un puerto del microcontrolador está conectado al visualizador de tal manera


que el bit 0 active el segmento 'a', el bit 1 active el segmento 'b', el bit 2 active
el segmento 'c' etc, entonces la tabla que sigue muestra la "máscara" para
cada dígito.

DÍGITOS A
S E G M E N T O S D E L V I S U A L I Z AD O R
V I S U AL I Z AR
dp a b c d e f g
0 0 1 1 1 1 1 1 0
1 0 0 1 1 0 0 0 0
2 0 1 1 0 1 1 0 1
3 0 1 1 1 1 0 0 1
4 0 0 1 1 0 0 1 1
5 0 1 0 1 1 0 1 1
6 0 1 0 1 1 1 1 1
7 0 1 1 1 0 0 0 0
8 0 1 1 1 1 1 1 1
9 0 1 1 1 1 0 1 1
Además de los dígitos de 0 a 9, hay algunas letras -A, C, E, J, F, U, H, L, b, c,
d, o, r, t - que se pueden visualizar al enmascarar.

En caso de que se utilicen los visualizadores de ánodo común, todos los unos
contenidos en la tabla anterior se deben sustituir por ceros y viceversa.
Además, los transistores PNP se deben utilizar como controladores.

OPTOACOPLADORES

Un optoacoplador es un dispositivo frecuentemente utilizado para aislar


galvánicamente el microcontrolador de corriente o voltaje potencialmente
peligroso de su entorno. Los optoacopladores normalmente disponen de una,
dos o cuatro fuentes de luz (diodos LED) en su entrada mientras que en su
salida, frente a los diodos, se encuentra el mismo número de los elementos
sensibles a la luz (foto-transistores, foto-tiristores, foto-triacs). El punto es que
un optoacoplador utiliza una corta ruta de transmisión óptica para transmitir una
señal entre los elementos de circuito, que están aislados eléctricamente. Este
aislamiento tiene sentido sólo si los diodos y los elementos foto-sensitivos se
alimentan por separado. Así, el microcontrolador y los componentes
adicionales y caros están completamente protegidos de alto voltaje y ruidos
que son la causa más frecuente de destrucción, daño y funcionamiento
inestable de los dispositivos electrónicos en la práctica. Los optoacopladores
utilizados con más frecuencia son aquéllos con foto-transistores en sus salidas.
En los optoacopladores con la base conectada al pin 6 interno (también hay
optoacopladores sin ella), la base puede quedarse desconectada.
La red R/C representada por una línea quebrada en la figura anterior indica una
conexión opcional de la base de transistores dentro del optoacoplador, que
reduce los efectos de ruidos al eliminar los pulsos muy cortos.

VISUALIZADOR LCD

Este componente está específicamente fabricado para ser utilizado con los
microcontroladores, lo que significa que no se puede activar por los circuitos
integrados estándar. Se utiliza para visualizar los diferentes mensajes en un
visualizador de cristal líquido miniatura. El modelo descrito aquí es el más
utilizado en la práctica por su bajo precio y grandes capacidades. Está basado
en el microcontrolador HD44780 (Hitachi) integrado y puede visualizar
mensajes en dos líneas con 16 caracteres cada una. Puede visualizar todas las
letras de alfabeto, letras de alfabeto griego, signos de puntuación, símbolos
matemáticos etc. También es posible visualizar símbolos creados por el
usuario. Entre otras características útiles es el desplazamiento automático de
mensajes (a la izquierda y a la derecha), aparición del cursor, retroiluminación
LED etc.
Pines del visualizador LCD

A lo largo de un lado de una placa impresa pequeña del visualizador LCD se


encuentran los pines que le permiten estar conectado al microcontrolador. Hay
14 pines en total marcados con números (16 si hay retroiluminación). Su
función se muestra en la tabla que sigue:

E S T AD O
FUNCIÓN NÚMERO NOMBRE DESCRIPCIÓN
LÓGICO
Tierra 1 Vss - 0V
Alimentación 2 Vdd - +5V
Contraste 3 Vee - 0 - Vdd
D0 – D7 considerado
0 comandos
4 RS
1 D0 – D7 considerado
datos
Escribir los datos
0 microcontrolador al
Control de 5 R/W
1 Leer los datos (del
funcionamiento
microcontrolador)
Acceso al visualizado
0
deshabilitado
1
6 E Funcionamiento
Transición de 1 a
Datos/comandos se
0
transmitiendo al LCD
7 D0 0/1 Bit 0 LSB
8 D1 0/1 Bit 1
9 D2 0/1 Bit 2
Datos / comandos
10 D3 0/1 Bit 3
11 D4 0/1 Bit 4
12 D5 0/1 Bit 5
13 D6 0/1 Bit 6
14 D7 0/1 Bit 7 MSB
Pantalla LCD

Una pantalla LCD puede visualizar dos líneas con 16 caracteres cada una.
Cada carácter consiste en 5x8 o 5x11 píxeles. Este libro cubre un visualizador
de 5x8 píxeles que es utilizado con más frecuencia.

El contraste del visualizador depende del voltaje de alimentación y de si los


mensajes se visualizan en una o dos líneas. Por esta razón, el voltaje variable
0-Vdd se aplica al pin marcado como Vee. Un potenciómetro trimer se utiliza
con frecuencia para este propósito. Algunos de los visualizadores LCD tienen
retroiluminación incorporada (diodos LED azules o verdes). Al utilizarlo durante
el funcionamiento, se debe de conectar una resistencia en serie a uno de los
pines para limitar la corriente (similar a diodos LED).
Si no hay caracteres visualizados o si todos los caracteres están oscurecidos al
encender el visualizador, lo primero que se debe hacer es comprobar el
potenciómetro para ajustar el contraste. ¿Está ajustado apropiadamente? Lo
mismo se aplica si el modo de funcionamiento ha sido cambiado (escribir en
una o en dos líneas).

Memoria LCD

El visualizador LCD dispone de tres bloques de memoria:

DDRAM Display Data RAM (RAM de datos de visualización);


CGRAM Character Generator RAM (generador de caracteres RAM); y
CGROM Character Generator ROM (generador de caracteres ROM)
Memoria DDRAM

La memoria DDRAM se utiliza para almacenar los caracteres a visualizar.


Tiene una capacidad de almacenar 80 caracteres. Algunas localidades de
memoria están directamente conectadas a los caracteres en el visualizador.

Todo funciona muy simple: basta con configurar el visualizador para


incrementar direcciones automáticamente (desplazamiento a la derecha) y
establecer la dirección inicial para el mensaje que se va a visualizar (por
ejemplo 00 hex).

Luego, todos los caracteres enviados por las líneas D0-D7 se van a visualizar
en el formato de mensaje al que nos hemos acostumbrado - de la izquierda a la
derecha. En este caso, la visualización empieza por el primer campo de la
primera línea ya que la dirección inicial es 00hex. Si se envía más de 16
caracteres, todos se memorizarán, pero sólo los primeros 16 serán visibles.
Para visualizar los demás, se debe utilizar el comando shift. Virtualmente,
parece como si el visualizador LCD fuera una ventana, desplazándose de la
izquierda a la derecha sobre las localidades de memoria con diferentes
caracteres. En realidad, así es cómo se creó el efecto de desplazar los
mensajes sobre la pantalla.
Si se habilita ver el cursor, aparecerá en la localidad actualmente direccionada.
En otras palabras, si un carácter aparece en la posición del cursor, se va a
mover automáticamente a la siguiente localidad direccionada.

Esto es un tipo de memoria RAM así que los datos se pueden escribir en ella y
leer de ella, pero su contenido se pierde irrecuperablemente al apagar la fuente
de alimentación.

Memoria CGROM

La memoria CGROM contiene un mapa estándar de todos los caracteres que


se pueden visualizar en la pantalla. A cada carácter se le asigna una localidad
de memoria:
Las direcciones de las localidades de memoria CGROM corresponden a los
caracteres ASCII. Si el programa que se está actualmente ejecutando llega al
comando ‘enviar el carácter P al puerto’, el valor binario 0101 0000 aparecerá
en el puerto. Este valor es el equivalente ASCII del carácter P. Al escribir este
valor en un LCD, se visualizará el símbolo de la localidad 0101 0000 de la
CGROM. En otras palabras, se visualizará el carácter P. Esto se aplica a todas
las letras del alfabeto (minúsculas y mayúsculas), pero no se aplica a los
números.

Como se muestra en el mapa anterior, las direcciones de todos los dígitos se


desplazan por 48 en relación con sus valores (dirección del dígito 0 es 48,
dirección del dígito 1 es 49, dirección del dígito 2 es 50 etc.). Por consiguiente,
para visualizar los dígitos correctamente es necesario añadir el número decimal
48 a cada uno antes de enviarlos a un LCD.

¿Qué es un código ASCII? Desde su aparición hasta hoy en día, las


computadoras han sido capaces de reconocer solamente números, y no las
letras. Esto significa que todos los datos que una computadora intercambia con
un periférico, reconocidos como letras por los humanos, en realidad están en el
formato binario (el teclado es un buen ejemplo). En otras palabras, a cada
carácter le corresponde la combinación única de ceros y unos. El código ASCII
representa una codificación de caracteres basada en el alfabeto inglés. El
ASCII especifica una correspondencia entre los símbolos de caracteres
estándar y sus equivalentes numéricos.

Memoria CGRAM

Además de los caracteres estándar, el visualizador LCD puede visualizar


símbolos definidos por el usuario. Esto puede ser cualquier símbolo de 5x8
píxeles. La memoria RAM denominada CGRAM de 64 bytes lo habilita.

Los registros de memoria son de 8 bits de anchura, pero sólo se utilizan 5 bits
más bajos. Un uno lógico (1) en cada registro representa un punto oscurecido,
mientras que 8 localidades agrupados representan un carácter. Esto se
muestra en la siguiente figura:

Los símbolos están normalmente definidos al principio del programa por una
simple escritura de ceros y unos de la memoria CGRAM así que crean las
formas deseadas. Para visualizarlos basta con especificar su dirección. Preste
atención a la primera columna en el mapa de caracteres CGROM. No contiene
direcciones de la memoria RAM, sino los símbolos de los que se está hablando
aquí. En este ejemplo ‘visualizar 0’ significa visualizar ‘sonrisa’, ‘visualizar 1’
significa - visualizar ‘ancla’ etc.

Comandos básicos del visualizador LCD

Todos los datos transmitidos a un visualizador LCD por las salidas D0-D7 serán
interpretados como un comando o un dato, lo que depende del estado lógico en
el pin RS:

 RS = 1 - Los bits D0 - D7 son direcciones de los caracteres a visualizar.


El procesador LCD direcciona un carácter del mapa de caracteres y lo
visualiza. La dirección DDRAM especifica la localidad en la que se va a
visualizar el carácter. Esta dirección se define antes de transmitir el
carácter o la dirección del carácter anteriormente transmitido será
aumentada automáticamente.
 RS = 0 - Los bits D0 - D7 son los comandos para ajustar el modo del
visualizador.
En la siguiente tabla se muestra una lista de comandos reconocidos por el
LCD:

TIEMPO
C O M AN D O RS RW D7 D6 D5 D4 D3 D2 D1 D0
EJECU
Borrar el
0 0 0 0 0 0 0 0 0 1 1.64mS
visualizador
Poner el cursor al
0 0 0 0 0 0 0 0 1 x 1.64mS
inicio
Modo de entrada 0 0 0 0 0 0 0 1 I/D S 40uS
Activar/desactivar el
0 0 0 0 0 0 1 D U B 40uS
visualizador
Desplazar el
0 0 0 0 0 1 D/C R/L x x 40uS
cursor/visualizador
Modo de
0 0 0 0 1 DL N F x x 40uS
funcionamiento
Establecer la
0 0 0 1 Dirección CGRAM 40uS
dirección CGRAM
Establecer la
0 0 1 Dirección CGRAM 40uS
dirección DDRAM
Leer la bandera
"BUSY"(ocupado) 0 1 BF Dirección CGRAM -
(BF)
Escribir en la
CGRAM o en la 1 0 D7 D6 D5 D4 D3 D2 D1 D0 40uS
DDRAM
Leer la CGRAM o la
1 1 D7 D6 D5 D4 D3 D2 D1 D0 40uS
DDRAM

I/D 1 = Incremento (por 1) R/L 1 = Desplazamiento a la


derecha
0 = Decremento (por 1) 0 = Desplazamiento a la
izquierda

S 1 = Desplazamiento del visualizador activado DL 1 = Bus de datos de 8


bits
0 = Desplazamiento del visualizador desactivado 0 = Bus de datos de 4
bits

D 1 = Visualizador encendido N 1 = Visualizador de dos


líneas
0 = Visualizador apagado 0 = Visualizador en una línea

U 1 = Cursor activado F 1 = Carácter de 5x10 puntos


0 = Cursor desactivado 0 = Carácter de 5x7 puntos

B 1 = Parpadeo del cursor encendido D/C 1 = Desplazamiento del


visualizador
0 = Parpadeo del cursor apagado 0 = Desplazamiento del
cursor

¿QUÉ ES UNA BANDERA DE OCUPADO (BUSY FLAG)?

En comparación al microcontrolador, el LCD es un componente


extremadamente lento. Por esta razón, era necesario proporcionar una señal
que, al ejecutar un comando, indicaría que el visualizador estaba listo para
recibir el siguiente dato. Esta señal denominada bandera de ocupado (busy
flag) se puede leer de la línea D7. El visualizador está listo para recibir un
nuevo dato cuando el voltaje en esta línea es de 0V (BF=0).

Conectar al visualizador LCD

Dependiendo de cuántas líneas se utilizan para conectar un LCD al


microcontrolador, hay dos modos de LCD, el de 8 bits y el de 4 bits. El modo
apropiado se selecciona en el inicio del funcionamiento en el proceso
denominado ‘inicialización’. El modo de LCD de 8 bits utiliza los pines D0-D7
para transmitir los datos, como hemos explicado en la página anterior. El
propósito principal del modo de LCD de 4 bits es de ahorrar los valiosos pines
de E/S del microcontrolador. Sólo los 4 bits más altos (D4-D7) se utilizan para
la comunicación, mientras que los demás pueden quedarse desconectados.
Cada dato se envía al LCD en dos pasos - primero se envían 4 bits más altos
(normalmente por las líneas D4- D7), y luego los 4 bits más bajos. La
inicialización habilita que el LCD conecte e interprete los bits recibidos
correctamente.

Pocas veces se leen los datos del LCD (por lo general se transmiten del
microcontrolador al LCD) así que, con frecuencia, es posible guardar un pin de
E/S de sobra. Es simple, basta con conectar el pin L/E a Tierra. Este “ahorro”
del pin tiene su precio. Los mensajes se visualizarán normalmente, pero no
será posible leer la bandera de ocupado ya que tampoco es posible leer los
datos del visualizador. Afortunadamente, hay una solución simple. Después de
enviar un carácter o un comando es importante dar al LCD suficiente tiempo
para hacer su tarea. Debido al hecho de que la ejecución de un comando
puede durar aproximadamente 1.64mS, el LCD tarda como máximo 2mS en
realizar su tarea.

Inicializar al visualizador LCD

Al encender la fuente de alimentación, el LCD se reinicia automáticamente.


Esto dura aproximadamente 15mS. Después de eso, el LCD está listo para
funcionar. Asimismo, el modo de funcionamiento está configurado por defecto
de la siguiente manera:

1. Visualizador está borrado.


2. Modo
DL = 1 - Bus de datos de 8 bits
N= 0 - LCD de una línea
F = 0 - Carácter de 5 x 8 puntos
3. Visualizador/Cursor encendido/apagado
D= 0 - Visualizador apagado
U= 0 - Cursor apagado
B = 0 - Parpadeo del cursor apagado
4. Introducción de caracteres
ID = 1 Direcciones visualizadas se incrementan automáticamente en 1
S = Desplazamiento del visualizador desactivado
Por lo general, el reinicio automático se lleva a cabo sin problemas. ¡En la
mayoría de los casos, pero no siempre! Si por cualquier razón, el voltaje de
alimentación no llega a su máximo valor en 10mS, el visualizador se pone a
funcionar de manera completamente imprevisible. Si la unidad de voltaje no es
capaz de cumplir con las condiciones o si es necesario proporcionar un
funcionamiento completamente seguro, se aplicará el proceso de inicialización.
La inicialización, entre otras cosas, reinicia de nuevo al LCD, al habilitarle un
funcionamiento normal.

Hay dos algoritmos de inicialización. Cuál se utilizará depende de si la conexión


al microcontrolador se realiza por el bus de datos de 4 o 8 bits. En ambos
casos, después de inicialización sólo queda especificar los comandos básicos
y, por supuesto, visualizar los mensajes.

Refiérase a la Figura que sigue para el procedimiento de inicialización por el


bus de datos de 8 bits:
¡Esto no es un error! En este algoritmo, el mismo valor se transmite tres veces
en fila.

El procedimiento de inicialización por el bus de datos de 4 bits:


Vamos a hacerlo en mikroC...

/* En mikroC for PIC, basta con escribir sólo una función para realizar todo el
proceso
de la inicialización del LCD. Antes de llamar esta función es necesario
declarar los
bits LCD_D4-LCD_D7, LCD_RS y LCD_EN. */

...
Lcd_Init(); // Inicializar el LCD
...

EJEMPLOS PRÁCTICOS

El proceso de crear un proyecto nuevo es muy simple. Seleccione la


opción New Project del menú Project como se muestra en la Figura de la
derecha.

Aparecerá una ventana que le guiará a través del proceso de creación de un


proyecto nuevo. La ventana de entrada de este programa contiene una lista de
acciones a realizar para crear un proyecto nuevo. Pulse el botón Next.
El proceso de creación de un proyecto nuevo consiste en cinco pasos:

1. Seleccione el tipo de microcontrolador a programar. En este caso se trata


del PIC16F887.
2. Seleccione la frecuencia de reloj del microcontrolador. En este caso el
valor seleccionado es 8 MHz.
3. Seleccione el nombre y la ruta del proyecto. En este caso, el nombre del
proyecto es First_Project. Está guardado en la carpeta C:\My projects. Al
nombre del proyecto se le asigna automáticamente la extensión .mcppi. Se
creará en el proyecto el archivo fuente con el mismo nombre (First_Project
.c.h).
4. Si el nuevo proyecto consiste de varios archivos fuente, se necesita
especificarlos y pulse sobre el botón Add para incluirlos. En este ejemplo
no hay archivos fuente adicionales.
5. Por último, se necesita confirmar todas las opciones seleccionadas. Pulse
sobre Finish.
Después de crear el proyecto, aparecerá una ventana blanca en la que debe
escribir el programa. Vea la siguiente figura:
Una vez creado el programa, es necesario compilarlo en un código .hex.
Seleccione una de las opciones para compilar del menú Project:
 Para crear un archivo .hex, seleccione la opción Build (Ctrl+F9) del menú
Project o pulse sobre el icono Build de la barra de herramientas Project.
 Por medio de la opción Build All Projects (Shift+F9) se compilan todos
los archivos del proyecto, librerías (si el código fuente contiene alguna
de ellas) y los archivos def para el microcontrolador utilizado.
 La opción Build + Program (Ctrl+F11) es importante ya que permite al
compilador mikroC PRO for PIC cargar automáticamente el programa en
el microcontrolador después de la compilación. El proceso de la
programación se realiza por medio del programador PICFlash.
Todos los errores encontrados durante la compilación aparecerán en la
ventana Message. Si no hay errores en el programa, el compilador mikroC
PRO for PIC generará los correspondientes archivos de salida.

4.3 EJEMPLO 1
Escribir cabecera, configurar pines de E/S, utilizar la función Delay y el
operador Switch

El único propósito de este programa es de encender varios diodos LED en el


puerto B. Utilice este ejemplo para examinar cómo es un programa real. La
siguiente figura muestra el esquema de conexión, mientras que el programa se
encuentra en la siguiente página.
Al encender la fuente de alimentación, cada segundo, el diodo LED en el puerto
B emite luz, lo que indica que el microcontrolador está conectado
correctamente y que funciona normalmente.

En este ejemplo se muestra cómo escribir una cabecera correctamente. Lo


mismo se aplica a todos los programas descritos en este libro. Para no repetir,
en los siguientes ejemplos no vamos a escribir la cabecera. Se considera estar
en el principio de cada programa, marcada como "Cabecera".
Para hacer este ejemplo más interesante, vamos a habilitar que los LEDs
conectados al puerto PORTB parpadeen. Hay varios modos de hacerlo:

1. Tan pronto como se encienda el microcontrolador, todos los LEDs emitirán


la luz por un segundo. La función Delay se encarga de eso en el programa.
Sólo se necesita ajustar la duración del tiempo de retardo en milisegundos.
2. Después de un segundo, el programa entra en el bucle for, y se queda allí
hasta que la variable k sea menor que 20. La variable se incrementa en 1
después de cada iteración. Dentro del bucle for, el operador switch
monitorea el estado lógico en el puerto PORTB. Si PORTB=0xFF, su
estado se invierte en 0x00 y viceversa. Cualquier cambio de estos estados
lógicos hace todos los LEDs parpadear. El ciclo de trabajo es 5:1
(500mS:100mS).
3. Al salir del bucle for, el estado lógico del puerto POTRB cambia (0xb
01010101) y el programa entra en el bucle infinito while y se queda allí
hasta que 1=1. El estado lógico del puerto PORTB se invierte cada
200mS.
4.4 EJEMPLO 2
Utilizar instrucciones en ensamblador y el oscilador interno LFINTOSC...

En realidad, esto es una continuación del ejemplo anterior, pero se ocupa de un


problema un poco más complicado... El propósito era hacer los LEDs en el
puerto PORTB parpadear lentamente. Se puede realizar al introducir un valor
suficiente grande para el parámetro del tiempo de retardo en la función Delay.
No obstante, hay otra manera más eficiente para ejecutar el programa
lentamente. Acuérdese de que este microcontrolador tiene un oscilador
incorporado LFINTOSC que funciona a una frecuencia de 31kHz. Ahora llegó la
hora de “darle una oportunidad”.

El programa se inicia con el bucle do-while y se queda allí por 20 ciclos.


Después da cada iteración, llega el tiempo de retardo de 100ms, indicado por
un parpadeo relativamente rápido de los LEDs en el puerto PORTB. Cuando el
programa salga de este bucle, el microcontrolador se inicia al utilizar el
oscilador LFINTOSC como una fuente de señal de reloj. Los LEDs parpadean
más lentamente aunque el programa ejecuta el mismo bucle do-while con un
tiempo de retardo 10 veces más corto.

Con el propósito de hacer evidentes algunas situaciones potencialmente


peligrosas, se activan los bits de control por medio de las instrucciones en
ensamblador. Dicho de manera sencilla, al entrar o salir una instrucción en
ensamblador en el programa, el compilador no almacena los datos en un banco
actualmente activo de la RAM, lo que significa que en esta sección de
programa, la selección de bancos depende del registro SFR utilizado. Al volver
a la sección de programa escrito en C, los bits de control RP0 y RP1 deben
recuperar el estado que tenían antes de ‘la aventura en ensamblador’. En este
programa, el problema se resuelve al utilizar la variable auxiliar saveBank, lo
que guarda el estado de estos dos bits.

/* Cabecera *********************************************/

int k = 0; // Variable k es de tipo int


char saveBank; // Variable saveBank es de tipo char

void main() {
ANSEL = 0; // Todos los pines de E/S se configuran como
digitales
ANSELH = 0;
PORTB = 0; // Todos los pines del puerto PORTB se ponen a 0
TRISB = 0; // Pines del puerto PORTB se configuran como
salidas

do {
PORTB = ~PORTB; // Invertir el estado lógico del puerto PORTB
Delay_ms(100); // Tiempo de retardo de 100mS
k++; // Incrementar k en 1
}
while(k<20); // Quedarse en bucle hasta que k<20

k=0; // Reiniciar variable k


saveBank = STATUS & 0b01100000; // Guardar el estado de los bits RP0 y
RP1

// (bits 5 y 6 del registro STATUS)


asm { // Inicio de una secuencia en ensamblador
bsf STATUS,RP0 // Seleccionar el banco de memoria que
contiene el
bcf STATUS,RP1 // registro OSCCON
bcf OSCCON,6 // Seleccionar el oscilador interno LFINTOSC
bcf OSCCON,5 // de frecuencia de 31KHz
bcf OSCCON,4
bsf OSCCON,0 // Microcontrolador utiliza oscilador interno
} // Final de la secuencia en ensamblador

STATUS &= 0b10011111; // Bits RP0 y RP1 recuperan el estado


original
STATUS |= saveBank;

do {
PORTB = ~PORTB; // Invertir el estado lógico del puerto PORTB
Delay_ms(10); // Tiempo de retardo de 10 mS
k++; // Incrementar k en 1
}
while(k<20); // Quedarse en el bucle hasta que k<20
}

4.5 EJEMPLO 3
Timer0 como un contador, declarar variables nuevas, constantes de
enumeración, utilizar relés...

En cuanto a los ejemplos anteriores, el microcontrolador ha ejecutado el


programa sin haber sido afectado de ninguna forma por su entorno. En la
práctica, hay pocos dispositivos que funcionen de esta manera (por ejemplo, un
simple controlador de luz de neón). Los pines de entrada se utilizan también en
este ejemplo. En la siguiente figura se muestra un esquema, mientras que el
programa está en la siguiente página. Todo sigue siendo muy simple. El
temporizador Timer0 se utiliza como un contador. La entrada del contador está
conectada a un botón de presión, así que cada vez que se presiona el botón, el
temporizador Timer0 cuenta un pulso.

Cuando el número de pulsos coincida con el número almacenado en el registro


TEST, un uno lógico (5V) aparecerá en el pin PORTD.3. Este voltaje activa un
relé electromecánico, y por eso este bit se le denomina ‘RELÉ’ en el programa.
/*Cabecera****************************************************/

void main() {
char TEST = 5; // Constante TEST = 5
enum salidas {RELÉ = 3}; // Constante RELAY = 3

ANSEL = 0; // Todos los pines de E/S se configuran como digitales


ANSELH = 0;
PORTA = 0; // Reiniciar el puerto PORTA
TRISA = 0xFF; // Todos los pines del puerto PORTA se configuran
como entradas
PORTD = 0; // Reiniciar el puerto PORTD
TRISD = 0b11110111; // Pin RD3 se configura como salida, mientras que
los demás

// se configuran como entradas


OPTION_REG.F5 = 1; // Contador TMR0 recibe los pulsos por el pin RA4
OPTION_REG.F3 = 1; // Valor del pre-escalador 1:1

TMR0 = 0; // Reiniciar el temporizador/contador TMR0

do {
if (TMR0 == TEST) // ¿Coincide el número en el temporizador con la
// constante TEST?
(PORTD.RELAY = 1); // Números coinciden. Poner el bit RD3 a uno
(salida RELÉ)
}
while (1); // Quedarse en el bucle infinito
}

Sólo una constante de enumeración RELÉ se utiliza en este ejemplo. Se le


asigna un valor mediante la declaración.

enum salidas {RELÉ = 3}; // Constante RELÉ = 3

Si varios pines del puerto PORTD están conectados a los relés, la expresión
anterior se puede escribir de la siguiente manera también:

enum salidas {RELÉ=3, CALENTADOR, MOTOR=6, SURTIDOR};

A todas las constantes, precedidas por las constantes con valores asignados
(RELÉ=3 y MOTOR=6), se les asignan automáticamente los valores de las
constantes precedentes, incrementados en 1. En este ejemplo, a las
constantes CALENTADOR y SURTIDOR se les asignan los valores 4 y 7, es
decir (CALENTADOR=4 y SURTIDOR=7), respectivamente.

4.6 EJEMPLO 4
Utilizar los temporizadores Timer0, Timer1 y Timer2. Utilizar
interrupciones, declarar nuevas funciones...

Al analizar los ejemplos anteriores, es probable que se haya fijado en la


desventaja de proporcionar tiempo de retardo por medio de la función Delay.
En estos casos, el microcontrolador se queda ‘estático’ y no hace nada.
Simplemente espera que transcurra una cierta cantidad de tiempo. Tal pérdida
de tiempo es un lujo inaceptable, por lo que se deberá aplicar otro método.

¿Se acuerda usted del capítulo de los temporizadores? ¿Se acuerda de lo de


interrupciones? Este ejemplo los conecta de una manera práctica. El esquema
se queda inalterada, y el desafío sigue siendo presente. Es necesario
proporcionar un tiempo de retardo suficiente largo para darse cuenta de los
cambios en el puerto. Para este propósito se utiliza el temporizador Timer0 con
el pre-escalador asignado. Siempre que se genere una interrupción con cada
desbordamiento en el registro del temporizador, la variable cnt se aumenta
automáticamente en 1 al ejecutarse cada rutina de interrupción. Cuando la
variable llega al valor 400, el puerto PORTB se incrementa en 1. Todo el
procedimiento se lleva a cabo “entre bastidores”, lo que habilita al
microcontrolador hacer otra tarea.

/*Cabecera******************************************************/

unsigned cnt; // Definir la variable cnt

void interrupt() {
cnt++; // Con una interrupción la cnt se incrementa en 1
TMR0 = 96; // El valor inicial se devuelve en el temporizador TMR0
INTCON = 0x20; // Bit T0IE se pone a 1, el bit T0IF se pone a 0
}

void main(){
OPTION_REG = 0x84; // Pre-escalador se le asigna al temporizador TMR0
ANSEL = 0; // Todos los pines de E/S se configuran como digitales
ANSELH = 0;
TRISB = 0; // Todos los pines de puerto PORTB se configuran

// como salidas
PORTB = 0x0; // Reiniciar el puerto PORTB
TMR0 = 96; // Temporizador T0 cuenta de 96 a 255
INTCON = 0xA0; // Habilitada interrupción TMR0
cnt = 0; // A la variable cnt se le asigna un 0

do { // Bucle infinito
if (cnt == 400) { // Incrementar el puerto PORTB después 400
interrupciones
PORTB = PORTB++; // Incrementar número en el puerto PORTB en 1
cnt = 0; // Reiniciar la variable cnt
}
} while(1);

Siempre que se produzca un desbordamiento en el registro del temporizador


TRM0, ocurre una interrupción.

/*Cabecera******************************************************/

unsigned short cnt; // Definir la variable cnt

void interrupt() {
cnt++ ; // Con una interrupción la cnt se incrementa en 1
PIR1.TMR1IF = 0; // Reiniciar el bit TMR1IF
TMR1H = 0x80; // El valor inicial se devuelve en los registros
TMR1L = 0x00; // del temporizador TMR1H y TMR1L
}

void main() {
ANSEL = 0; // Todos los pines de E/S se configuran como digitales
ANSELH = 0;
PORTB = 0xF0; // Valor inicial de los bits del puerto PORTB
TRISB = 0; // Pines del puerto PORTB se configuran como salidas
T1CON = 1; // Configurar el temporizador TMR1
PIR1.TMR1IF = 0; // Reiniciar el bit TMR1IF
TMR1H = 0x80; // Ajustar el valor inicial del temporizador TMR1
TMR1L = 0x00;
PIE1.TMR1IE = 1; // Habilitar la interrupción al producirse un
desbordamiento
cnt = 0; // Reiniciar la variable cnt
INTCON = 0xC0; // Interrupción habilitada (bits GIE y PEIE)

do { // Bucle infinito
if (cnt == 76) { // Cambiar el estado del puerto PORTB después de 76
interrupciones
PORTB = ~PORTB; // Número en el puerto PORTB está invertido
cnt = 0; // Reiniciar la variable cnt
}
} while (1);
}

En este caso, una interrupción se habilita después de que se produzca un


desbordamiento en el registro del temporizador TMR1 (TMR1H, TMR1L).
Además, la combinación de los bits que varía en el puerto POTRB difiere de la
en el ejemplo anterior.

/*Cabecera******************************************************/

unsigned short cnt; // Definir la variable cnt

void Reemplazar() {
PORTB = ~PORTB; // Definir nueva función ‘Reemplazar’
} // Función invierte el estado del puerto

void interrupt() {
if (PIR1.TMR2IF) { // Si el bit TMR2IF = 1,
cnt++ ; // Incrementar variable la cnt en 1
PIR1.TMR2IF = 0; // Reiniciar el bit y
TMR2 = 0; // Reiniciar el registro TMR2
}
}

// main
void main() {
cnt = 0; // Reiniciar la variable cnt
ANSEL = 0; // Todos los pines de E/S se configuran como digitales
ANSELH = 0;
PORTB = 0b10101010; // Estado lógico en los pines del puerto PORTB
TRISB = 0; // Todos los pines del puerto PORTB se configuran como
salidas
T2CON = 0xFF; // Configurar el temporizador T2
TMR2 = 0; // Valor inicial del registro del temporizador TMR2
PIE1.TMR2IE = 1; // Interrupción habilitada
INTCON = 0xC0; // Bits GIE y PEIE se ponen a 1

while (1) { // Bucle infinito


if (cnt > 30) { // Cambiar el estado del puerto PORTB después de
// más de 30 interrupciones
Reemplazar(); // Función Reemplazar invierte el estado del puerto
PORTB
cnt = 0; // Reiniciar la variable cnt
}
}
}

En este ejemplo, una interrupción ocurre después de que se produce un


desbordamiento en el registro del temporizador TMR2. Para invertir el estado
lógico de los pines del puerto se utiliza la función Reemplazar, que
normalmente no pertenece al lenguaje C estándar.

4.7 EJEMPLO 5
Utilizar el temporizador perro - guardián

Este ejemplo muestra cómo NO se debe utilizar el temporizador perro-


guardián. Un comando utilizado para reiniciar este temporizador se omite a
propósito en el bucle del programa principal, lo que habilita al temporizador
perro guardián “ganar la batalla del tiempo” y reiniciar al microcontrolador. Por
consiguiente, el microcontrolador se va a reiniciar sin parar, lo que indicará el
parpadeo de los LEDs del puerto PORTB.
/*Cabecera******************************************************/

void main() {
OPTION_REG = 0x0E; // Pre-escalador se le asigna al temporizador WDT
(1:64)
asm CLRWDT; // Comando en ensamblador para reiniciar el
temporizador WDT
PORTB = 0x0F; // Valor inicial del registro PORTB
TRISB = 0; // Todos los pines del puerto PORTB se configuran como
salidas
Delay_ms(300); // Tiempo de retardo de 30mS
PORTB = 0xF0; // Valor del puerto PORTB diferente del inicial

while (1); // Bucle infinito. El programa se queda aquí hasta que el


// temporizador WDT reinicie al microcontrolador
}

Para que este ejemplo funcione apropiadamente, es necesario habilitar al


temporizador perro-guardián al seleccionar la opción Tools/mE
Programmer/Watchdog Timer - Enabled.
4.8 EJEMPLO 6
Módulo CCP1 como generador de señal PWM

Este ejemplo muestra el uso del módulo CCP1 en modo PWM. Para hacer las
cosas más interesantes, la duración de los pulsos en la salida P1A (PORTC,2)
se puede cambiar por medio de los botones de presión simbólicamente
denominados ‘OSCURO’ y ‘CLARO’. La duración ajustada se visualiza como
una combinación binaria en el puerto PORTB. El funcionamiento de este
módulo está bajo el control de las funciones pertenecientes a la librería
especializada PWM. Aquí se utilizan las tres de ellas:

1. PWM1_init tiene el prototipo: void Pwm1_Init(long freq);


El parámetro freq ajusta la frecuencia de la señal PWM expresada en
hercios. En este ejemplo equivale a 5kHz.
2. PWM1_Start tiene el prototipo: void Pwm1_Start(void);
3. PWM1_Set_Duty tiene el prototipo: void Pwm1_Set_Duty(unsigned
short duty_ratio);
El parámetro duty_ratio ajusta la duración de pulsos en una secuencia de
pulsos.
La librería PWM también contiene la función PWM_Stop utilizada para
deshabilitar este modo. Su prototipo es: void Pwm1_Stop(void);
/*Cabecera******************************************************/

// Definir las variables ciclo_de_trabajo_actual,


// ciclo_de trabajo_anterior

unsigned short ciclo_de_trabajo_actual;


unsigned short ciclo_de trabajo_anterior;

void initMain() {
ANSEL = 0; // Todos los pines de E/S se configuran como digitales
ANSELH = 0;
PORTA = 255; // Estado inicial del puerto PORTA
TRISA = 255; // Todos los pines del puerto PORTA se configuran como
entradas
PORTB = 0; // Estado inicial del puerto PORTB
TRISB = 0; // Todos los pines del puerto PORTB se configuran como
salidas
PORTC = 0; // Estado inicial del puerto PORTC
TRISC = 0; // Todos los pines del puerto PORTC se configuran

// como salidas
PWM1_Init(5000); // Inicialización del módulo PWM (5KHz)
}

void main() {
initMain();
ciclo_de_trabajo_actual = 16; // Valor inicial de la variable
ciclo_de_trabajo_actual
ciclo_de trabajo_anterior = 0; // Reiniciar la variable ciclo_de
trabajo_anterior
PWM1_Start(); // Iniciar el módulo PWM1

while (1) { // Bucle infinito


if (Button(&PORTA, 0,1,1)) // Si se presiona el botón conectado al RA0
ciclo_de_trabajo_actual++ ; // incrementar el valor de la variable
current_duty
if (Button(&PORTA, 1,1,1)) // Si se presiona el botón conectado al RA1
ciclo_de_trabajo_actual-- ; // decrementar el valor de la variable
current_duty

if (old_duty != ciclo_de_trabajo_actual) { // Si
ciclo_de_trabajo_actual y
// ciclo_de trabajo_anterior no son iguales
PWM1_Set_Duty(ciclo_de_trabajo_actual); // ajustar un nuevo
valor a PWM,
ciclo_de trabajo_anterior = ciclo_de_trabajo_actual; // Guardar el nuevo
valor
PORTB = ciclo_de trabajo_anterior; // y visualizarlo en el
puerto PORTB
}
Delay_ms(200); // Tiempo de retardo de 200mS
}
}

Para que este ejemplo funcione apropiadamente, es necesario marcar las


siguientes librerías en la ventana Library Manager antes de compilar el
programa:

 PWM
 Button
4.9 EXAMPLE 7
Utilizar el convertidor A/D

El convertidor A/D del microcontrolador PIC16F887 se utiliza en este ejemplo.


¿Hace falta decir que todo es pan comido? Una señal analógica variable se
aplica al pin AN2, mientras que el resultado de la conversión de 10 bits se
muestra en los puertos POTRB y PORTD (8 bits menos significativos en el
puerto PORTD y 2 bits más significativos en el puerto PORTB). La Tierra
(GND) se utiliza como voltaje de referencia bajo Vref-, mientras que el voltaje
de referencia alto se aplica al pin AN3. Esto habilita que la escala de medición
se estire y encoja.
IEn otras palabras, el convertidor A/D siempre genera un resultado binario de
10 bits, lo que significa que reconoce 1024 niveles de voltaje en total
(210=1024). La diferencia entre dos niveles de voltaje no es siempre la misma.
Cuánto menor sea la diferencia entre Vref+ y Vref-, tanto menor será la
diferencia entre dos de 1024 niveles. Como hemos visto, el convertidor A/D es
capaz de detectar pequeños cambios de voltaje.

/*Cabecera******************************************************/

unsigned int temp_res;

void main() {
ANSEL = 0x0C; // Pines AN2 y AN3 se configuran como analógicos
TRISA = 0xFF; // Todos los pines del puerto PORTA se configuran

// como entradas
ANSELH = 0; // Los demás pines se configuran como digitales
TRISB = 0x3F; // Pines del puerto PORTB, RB7 y RB6 se configuran
// como salidas
TRISD = 0; // Todos los pines del PORTD se configuran como
salidas
ADCON1.F4 = 1 ; // Voltaje de referencia es llevado al pin RA3.

do {
temp_res = ADC_Read(2); // Resultado de la conversión A/D es copiado a
temp_res
PORTD = temp_res; // 8 bits menos significativos se mueven al puerto
PORTD
PORTB = temp_res >> 2; // 2 bits más significativos se mueven a los bits
RB6 y RB7
} while(1); // Bucle infinito
}

Para que este ejemplo funcione apropiadamente, es necesario marcar la


librería ADC en la ventana Library Manager antes de compilar el programa:

 ADC
4.10 EJEMPLO 8
Utilizar memoria EEPROM

Este ejemplo muestra cómo escribir y leer la memoria EEPROM incorporada.


El programa funciona de la siguiente manera. El bucle principal lee
constantemente el contenido de localidad de la memoria EEPROM en la
dirección 5 (decimal). Luego el programa entra en el bucle infinito en el que el
puerto PORTB se incrementa y se comprueba el estado de entradas del puerto
PORTA.2. En el momento de presionar el botón denominado MEMO, un
número almacenado en el puerto PORTB será guardado en la memoria
EEPROM, directamente leído y visualizado en el puerto PORTD en forma
binaria.
/*Cabecera******************************************************/

void main() {{
ANSEL = 0; // Todos los pines de E/S se configuran como digitales
ANSELH = 0;
PORTB = 0; // Valor inicial del puerto PORTB
TRISB = 0; // Todos los pines del puerto PORTB se configuran

// como salidas
PORTD = 0; // Valor inicial del puerto PORTB
TRISD = 0; // Todos los pines del puerto PORTD se configuran

// como salidas
TRISA = 0xFF; // Todos los pines del puerto PORTA se configuran

// como entradas
PORTD = EEPROM_Read(5); // Leer la memoria EEPROM en la dirección
5

do {
PORTB = PORTB++; // Incrementar el puerto PORTB en 1
Delay_ms(100); // Tiempo de retardo de 100mS

if (PORTA.F2)
EEPROM_Write(5,PORTB); // Si se pulsa el botón MEMO, guardar el
puerto PORTB

PORTD = EEPROM_Read(5); // Leer el dato escrito

do {
while (PORTA.F2); // Quedarse en este bucle hasta que el botón esté
pulsado
}
}
while(1); // Bucle infinito
}

Para comprobar el funcionamiento de este circuito, basta con pulsar el botón


MEMO y apagar el dispositivo. Después de reiniciar el dispositivo, el programa
visualizará el valor guardado en el puerto PORTD. Acuérdese de que en el
momento de escribir, el valor fue visualizado en el puerto PORTB.

Para que este ejemplo funcione apropiadamente, es necesario marcar la


librería EEPROM en la ventana Library Manager antes de compilar el
programa:

EEPROM
4.11 EJEMPLO 9
Contador de dos dígitos LED, multiplexión

En este ejemplo el microcontrolador funciona como un contador de dos dígitos.


La variable i se incrementa (suficiente lentamente para ser visible) y su valor se
visualiza en un visualizador de dos dígitos LED (99-0). El punto es habilitar una
conversión de un número binario en un decimal y partirlo en dos dígitos (en
decenas y unidades). Como los segmentos del visualizador LED se conectan
en paralelo, es necesario asegurar que alternen rápidamente para tener una
impresión de que emiten la luz simultáneamente (multiplexión por división en
tiempo).

En este ejemplo, el temporizador TMR0 está encargado de la multiplexión por


división en tiempo, mientras que la función mask convierte un número binario a
formato decimal.

/*Cabecera******************************************************/

unsigned short mask(unsigned short num);


unsigned short digit_no, digit10, digit1, digit, i;

void interrupt() {
if (digit_no == 0) {
PORTA = 0; // Apagar ambos visualizadores
PORTD = digit1; // Colocar máscara para visualizar unidades en el

// puerto PORTD
PORTA = 1; // Encender el visualizador para las unidades (LSD)
digit_no = 1;
}else{
PORTA = 0; // Apagar ambos visualizadores
PORTD = digit10; // Colocar máscara para visualizar decenas en el

// puerto PORTD
PORTA = 2; // Encender el visualizador para las decenas (MSD)
digit_no = 0;
}

TMR0 = 0; // Reiniciar el contador TMRO


INTCON = 0x20; // Bit T0IF=0, T0IE=1
}

void main() {
OPTION_REG = 0x80; // Ajustar el temporizador TMR0
TMR0 = 0;
INTCON = 0xA0; // Deshabilitar las interrupciones PEIE,INTE,RBIE,T0IE
PORTA = 0; // Apagar ambos visualizadores
TRISA = 0; // Todos los pines del puerto PORTA se configuran

// como salidas
PORTD = 0; // Apagar todos los segmentos del visualizador
TRISD = 0; // Todos los pines del puerto PORTD se configuran

// como salidas
do {
for (i = 0; i<=99; i++) { // Contar de 0 a 99
digit = i % 10u;
digit1 = mask(digit); // Preparar la máscara para visualizar unidades
digit = (char)(i / 10u) % 10u;
digit10 = mask(digit); // Preparar la máscara para visualizar decenas
Delay_ms(1000);
}
} while (1); // Bucle infinito
}

mask.c

/*Cabecera******************************************************/

unsigned short mask(unsigned short num) {


switch (num) {
case 0 : return 0x3F;
case 1 : return 0x06;
case 2 : return 0x5B;
case 3 : return 0x4F;
case 4 : return 0x66;
case 5 : return 0x6D;
case 6 : return 0x7D;
case 7 : return 0x07;
case 8 : return 0x7F;
case 9 : return 0x6F;
}
}

Para que este ejemplo funcione apropiadamente, es necesario incluir el archivo


mask.c en el proyecto (aparte del archivo example9.c) en la ventana Project
Manager antes de compilar el programa:

Example9.mcppi - Sources - Add File To Project


 mask.c
 example9.c
4.12 EJEMPLO 10
Utilizar el visualizador LCD

Este ejemplo muestra cómo utilizar un visualizador LCD alfanumérico. Las


librerías de funciones simplifican este programa, lo que significa que al final el
esfuerzo para crear el software vale la pena.

Un mensaje escrito en dos líneas aparece en el visualizador:

mikroElektronika
LCD example
Dos segundos más tarde, el mensaje en la segunda línea cambia, y se
visualiza el voltaje presente en la entrada del convertidor A/D (el pin RA2). Por
ejemplo:

mikroElektronika
voltage:3.141V
En un dispositivo real se puede visualizar temperatura actual o algún otro valor
medido en vez de voltaje.
Para que este ejemplo funcione apropiadamente, es necesario marcar las
siguientes librerías en la ventana Library Manager antes de compilar el
programa:

 ADC
 LCD

/*Cabecera*****************************************************/

// Conexiones del módulo LCD


sbit LCD_RS at RB4_bit;
sbit LCD_EN at RB5_bit;
sbit LCD_D4 at RB0_bit;
sbit LCD_D5 at RB1_bit;
sbit LCD_D6 at RB2_bit;
sbit LCD_D7 at RB3_bit;
sbit LCD_RS_Direction at TRISB4_bit;
sbit LCD_EN_Direction at TRISB5_bit;
sbit LCD_D4_Direction at TRISB0_bit;
sbit LCD_D5_Direction at TRISB1_bit;
sbit LCD_D6_Direction at TRISB2_bit;
sbit LCD_D7_Direction at TRISB3_bit;
// Final de las conexiones del módulo LCD

// Declarar variables
unsigned char ch;
unsigned int adc_rd;
char *text;
long tlong;

void main() {
INTCON = 0; // Todas las interrupciones deshabilitadas
ANSEL = 0x04; // Pin RA2 se configura como una entrada
analógica
TRISA = 0x04;
ANSELH = 0; // Los demás pines se configuran como digitales

Lcd_Init(); // Inicialización del visualizador LCD


Lcd_Cmd(_LCD_CURSOR_OFF); // Comando LCD (apagar el cursor)
Lcd_Cmd(_LCD_CLEAR); // Comando LCD (borrar el LCD)

text = "mikroElektronika"; // Definir el primer mensaje


Lcd_Out(1,1,text); // Escribir el primer mensaje en la primera línea

text = "LCD example"; // Definir el segundo mensaje


Lcd_Out(2,1,text); // Definir el primer mensaje

ADCON1 = 0x82; // Voltaje de referencia para la conversión A/D es


VCC
TRISA = 0xFF; // Todos los pines del puerto PORTA se configuran
como entradas
Delay_ms(2000);

text = "voltage:"; // Definir el tercer mensaje

while (1) {
adc_rd = ADC_Read(2); // Conversión A/D. Pin RA2 es una entrada.
Lcd_Out(2,1,text); // Escribir el resultado en la segunda línea
tlong = (long)adc_rd * 5000; // Convertir el resultado en milivoltios
tlong = tlong / 1023; // 0..1023 -> 0-5000mV
ch = tlong / 1000; // Extraer voltios (miles de milivoltios)

// del resultado
Lcd_Chr(2,9,48+ch); // Escribir resultado en formato ASCII
Lcd_Chr_CP('.');
ch = (tlong / 100) % 10; // Extraer centenas de milivoltios
Lcd_Chr_CP(48+ch); // Escribir resultado en formato ASCII
ch = (tlong / 10) % 10; // Extraer decenas de milivoltios
Lcd_Chr_CP(48+ch); // Escribir resultado en formato ASCII
ch = tlong % 10; // Extraer unidades de milivoltios
Lcd_Chr_CP(48+ch); // Escribir resultado en formato ASCII
Lcd_Chr_CP('V');
Delay_ms(1);
}
}

4.13 EJEMPLO 11
Comunicación serial RS-232

Este ejemplo muestra cómo utilizar el módulo EUSART del microcontrolador.


La conexión a una PC se habilita por medio del estándar de comunicación RS-
232. El programa funciona de la siguiente manera. Cada byte recibido por
medio de la comunicación serial se visualiza al utilizar los LEDs conectados al
puerto PORTB y después se devuelve automáticamente al transmisor. Si
ocurre un error en recepción, se lo indicará al encender el diodo LED. La
manera más fácil es comprobar el funcionamiento del dispositivo en la práctica
al utilizar un programa estándar de Windows denominado Hyper Terminal.

/*Cabecera******************************************************/
unsigned short i;

void main() {
UART1_Init(19200); // Inicializar el módulo USART
// (8 bits, tasa de baudios 19200, no hay bit
// de paridad...)

while (1) {
if (UART1_Data_Ready()) { // si se ha recibido un dato
i = UART1_Read(); // leerlo
UART1_Write(i); // enviarlo atrás
}
}
}

Para que este ejemplo funcione apropiadamente, es necesario marcar la


librería UART en la ventana Library Manager antes de compilar el programa:

UART
4.14 EJEMPLO 12
Medición de temperatura por medio del sensor DS1820. Uso del protocolo
‘1-wire’...

La medición de temperatura es una de las tareas más frecuentes realizadas por


el microcontrolador. En este ejemplo, se utiliza un sensor DS1820 para medir.
Es capaz de medir en el rango de 55 °C a 125 °C con exactitud de 0.5 °C. Para
transmitir los datos al microcontrolador se utiliza un tipo especial de la
comunicación serial denominado 1-wire. Debido al hecho de que estos
sensores son simples de utilizar y de grandes capacidades, los comandos
utilizados para hacerlos funcionar y controlarlos tienen la forma de funciones
almacenadas en la librería One_Wire. En total son las siguientes tres
funciones:

 Ow_Reset se utiliza para reiniciar el sensor;


 Ow_Read se utiliza para recibir los datos del sensor; y
 Ow_Write se utiliza para enviar los comandos al sensor
Este ejemplo muestra la ventaja de utilizar librerías con las funciones listas
para ser utilizadas. Concretamente, no tiene que examinar la documentación
proporcionada por el fabricante para utilizar el sensor. Basta con copiar alguna
de estas funciones en el programa. Si le interesa saber cómo se declaran,
basta con pulsar sobre alguna de ellas y seleccione la opción Help.

/*Cabecera******************************************************/

// Conexiones del módulo LCD


sbit LCD_RS at RB4_bit;
sbit LCD_EN at RB5_bit;
sbit LCD_D4 at RB0_bit;
sbit LCD_D5 at RB1_bit;
sbit LCD_D6 at RB2_bit;
sbit LCD_D7 at RB3_bit;
sbit LCD_RS_Direction at TRISB4_bit;
sbit LCD_EN_Direction at TRISB5_bit;
sbit LCD_D4_Direction at TRISB0_bit;
sbit LCD_D5_Direction at TRISB1_bit;
sbit LCD_D6_Direction at TRISB2_bit;
sbit LCD_D7_Direction at TRISB3_bit;
// Final de conexiones del módulo LCD

const unsigned short TEMP_RESOLUTION = 9;


char *text = "000.0000";
unsigned temp;
void Display_Temperature(unsigned int temp2write) {
const unsigned short RES_SHIFT = TEMP_RESOLUTION - 8;
char temp_whole;
unsigned int temp_fraction;

// comprobar si la temperatura es negativa


if (temp2write & 0x8000) {
text[0] = '-';
temp2write = ~temp2write + 1;
}

// extraer temp_whole
temp_whole = temp2write >> RES_SHIFT ;

// convertir temp_whole en caracteres


if (temp_whole/100)
text[0] = temp_whole/100 + 48;
else
text[0] = '0';

text[1] = (temp_whole/10)%10 + 48; // Extraer dígito de decenas


text[2] = temp_whole%10 + 48; // Extraer dígito de unidades

// extraer temp_fraction y convertirlo en unsigned int


temp_fraction = temp2write << (4-RES_SHIFT);
temp_fraction &= 0x000F;
temp_fraction *= 625;

// convertir temp_fraction en caracteres


text[4] = temp_fraction/1000 + 48; // Extraer dígito de miles
text[5] = (temp_fraction/100)%10 + 48; // Extraer dígito de centenas
text[6] = (temp_fraction/10)%10 + 48; // Extraer dígito de decenas
text[7] = temp_fraction%10 + 48; // Extraer dígito de unidades

// Visualizar temperatura en el LCD


Lcd_Out(2, 5, text);
}

void main() {
ANSEL = 0; // Configurar los pines AN como digitales
ANSELH = 0;
C1ON_bit = 0; // Deshabilitar los comparadores
C2ON_bit = 0;

Lcd_Init(); // Inicializar el LCD


Lcd_Cmd(_LCD_CLEAR); // Borrar el LCD
Lcd_Cmd(_LCD_CURSOR_OFF); // Apagar el cursor
Lcd_Out(1, 1, " Temperatura: ");
// Visualizar el carácter de grado, 'C' para centígrados
Lcd_Chr(2,13,223); // distintos visualizadores LCD tienen diferentes códigos

// de caracteres para el grado


// si ve la letra griega Alfa, intente introducir 178 en vez de 223
Lcd_Chr(2,14,'C');

//--- bucle principal


do {
//--- realizar lectura de temperatura
Ow_Reset(&PORTE, 2); // Señal de reinicio de Onewire
Ow_Write(&PORTE, 2, 0xCC); // Ejecutar el comando SKIP_ROM
Ow_Write(&PORTE, 2, 0x44); // Ejecutar el comando CONVERT_T
Delay_us(120);
Ow_Reset(&PORTE, 2);
Ow_Write(&PORTE, 2, 0xCC); // Ejecutar el comando SKIP_ROM
Ow_Write(&PORTE, 2, 0xBE); // Ejecutar el comando
READ_SCRATCHPAD
temp = Ow_Read(&PORTE, 2);
temp = (Ow_Read(&PORTE, 2) << 8) + temp;

//--- Formatear y visualizar el resultado en el LCD


Display_Temperature(temp);
Delay_ms(500);
} while (1);
}

Para que este ejemplo funcione apropiadamente, es necesario marcar las


siguientes librerías en la ventana Library Manager antes de compilar el
programa:

One_Wire
LCD
4.15 EXAMPLE 13
Generación de sonido, librería de sonido...

Las señales de audio se utilizan con frecuencia cuando se necesita llamar la


atención de usuario, confirmar que alguno de los botones se ha pulsado, avisar
que se ha llegado hasta los valores mínimos o máximos etc. Pueden ser una
simple señal de pitido así como melodías de una duración más larga o más
corta. En este ejemplo se muestra la generación de sonido por medio de
funciones que pertenecen a la librería Sound.
Además de estas funciones, la función Button que pertenece a la misma librería
se utiliza para probar los botones de presión.

/*Cabecera******************************************************/

void Tone1() {
Sound_Play(659, 250); // Frecuencia = 659Hz, duración = 250ms
}

void Tone2() {
Sound_Play(698, 250); // Frecuencia = 698Hz, duración = 250ms
}

void Tone3() {
Sound_Play(784, 250); // Frecuencia = 784Hz, duración = 250ms
}

void Melody1() { // Componer una melodía divertida 1


Tone1(); Tone2(); Tone3(); Tone3();
Tone1(); Tone2(); Tone3(); Tone3();
Tone1(); Tone2(); Tone3();
Tone1(); Tone2(); Tone3(); Tone3();
Tone1(); Tone2(); Tone3();
Tone3(); Tone3(); Tone2(); Tone2(); Tone1();
}

void ToneA() { // Tono A


Sound_Play( 880, 50);
}

void ToneC() { // Tono C


Sound_Play(1046, 50);
}

void ToneE() { // Tono E


Sound_Play(1318, 50);
}

void Melody2() { // Componer una melodía divertida 2


unsigned short i;

for (i = 9; i > 0; i--) {


ToneA(); ToneC(); ToneE();
}
}

void main() {
ANSEL = 0; // Todos los pines de E/S son digitales
ANSELH = 0;
TRISB = 0xF0; // Pines RB7-RB4 se configuran como entradas

// RB3 se configura como salida


Sound_Init(&PORTB, 3);
Sound_Play(1000, 500);

while (1) {
if (Button(&PORTB,7,1,1)) // RB7 genera Tono1
Tone1();
while (PORTB & 0x80) ; // Esperar que se suelte el botón
if (Button(&PORTB,6,1,1)) // RB6 genera Tono2
Tone2();
while (PORTB & 0x40) ; // Esperar que se suelte el botón
if (Button(&PORTB,5,1,1)) // RB5 genera melodía 2
Melody2();
while (PORTB & 0x20) ; // Esperar que se suelte el botón
if (Button(&PORTB,4,1,1)) // RB4 genera melodía 1
Melody1();
while (PORTB & 0x10) ; // Esperar que se suelte el botón
}
}
Para que este ejemplo funcione apropiadamente, es necesario marcar las
siguientes librerías en la ventana Library Manager antes de compilar el
programa:

 Button
 Sound
4.16 EJEMPLO 14
Utilizar el visualizador LCD gráfico

Un LCD gráfico (GLCD) proporciona un método avanzado para visualizar


mensajes. Mientras que un LCD de caracteres puede visualizar sólo caracteres
alfanuméricos, el LCD gráfico puede visualizar los mensajes en forma de
dibujos y mapas de bits. El LCD gráfico utilizado con más frecuencia tiene una
resolución de pantalla de 128x64 píxeles. El contraste de un GLCD se puede
ajustar por medio del potenciómetro P1.

En este ejemplo el GLCD visualiza una secuencia de imágenes, de las que un


mapa de bits representa un camión almacenado en otro archivo denominado
truck_bmp.c. Para habilitar que este programa funcione apropiadamente, usted
debe añadir este archivo como archivo fuente a su proyecto.

Las directivas del preprocesador incluidas en este ejemplo le permiten elegir si


quiere visualizar toda la secuencia de imágenes o sólo una secuencia corta.
Por defecto se visualizará toda la secuencia de imágenes. Sin embargo, al
añadir un comentario delante de la directiva #define COMPLETE_EXAMPLE se
deshabilita visualizar algunas imágenes de la secuencia. Si se comenta (o si se
borra) esta directiva, las sentencias entre las directivas #ifdef
COMPLETE_EXAMPLE y #endif no serán compiladas, así que no se
ejecutarán.

/*Cabecera******************************************************/

//Declaraciones-----------------------------------------------------------------
const code char truck_bmp[1024]; // Declarar la constante definida en
truck_bmp.c
// para utilizarla en este archivo
//--------------------------------------------------------final-de-declaraciones

// Conexiones del módulo Glcd


char GLCD_DataPort at PORTD;
sbit GLCD_CS1 at RB0_bit;
sbit GLCD_CS2 at RB1_bit;
sbit GLCD_RS at RB2_bit;
sbit GLCD_RW at RB3_bit;
sbit GLCD_EN at RB4_bit;
sbit GLCD_RST at RB5_bit;
sbit GLCD_CS1_Direction at TRISB0_bit;
sbit GLCD_CS2_Direction at TRISB1_bit;
sbit GLCD_RS_Direction at TRISB2_bit;
sbit GLCD_RW_Direction at TRISB3_bit;
sbit GLCD_EN_Direction at TRISB4_bit;
sbit GLCD_RST_Direction at TRISB5_bit;
// Final de conexiones del módulo Glcd

void delay2S(){ // Función de tiempo de retardo de 2 segundos


Delay_ms(2000);
}

void main() {
unsigned short ii;
char *someText;

/* Esta directiva define un macro denominado COMPLETE_EXAMPLE. Más


tarde en el
programa, la directiva ifdef prueba si este macro está definido. Si se borra
esta
línea o si se transforma en un comentario, las secciones del código entre las
directivas ifdef y endif no serán compiladas. */

#define COMPLETE_EXAMPLE // Poner esta línea como un comentario si


quiere
// visualizar la versión corta de la secuencia
ANSEL = 0; // Configurar los pines AN como digitales
ANSELH = 0;
C1ON_bit = 0; // Deshabilitar comparadores
C2ON_bit = 0;
Glcd_Init(); // Inicializar el GLCD
Glcd_Fill(0x00); // Borrar el GLCD

while(1) { // bucle infinito, la secuencia se repite

/* Dibujar la primera imagen */


#ifdef COMPLETE_EXAMPLE
Glcd_Image(truck_bmp); // Dibujar la imagen
delay2S(); delay2S();
#endif
Glcd_Fill(0x00); // Borrar el GLCD

/* Dibujar la segunda imagen */


Glcd_Box(62,40,124,56,1); // Dibujar la caja
Glcd_Rectangle(5,5,84,35,1); // Dibujar el rectángulo
Glcd_Line(0, 0, 127, 63, 1); // Dibujar la línea

delay2S();

for(ii = 5; ii < 60; ii+=5 ){ // Dibujar líneas horizontales y verticales


Delay_ms(250);
Glcd_V_Line(2, 54, ii, 1);
Glcd_H_Line(2, 120, ii, 1);
}

delay2S();
Glcd_Fill(0x00); // Borrar el GLCD

/* Dibujar la tercera imagen */

#ifdef COMPLETE_EXAMPLE
Glcd_Set_Font(Character8x7, 8, 7, 32); // Seleccionar la fuente, ver
// __Lib_GLCDFonts.c en la carpeta Uses
#endif

Glcd_Write_Text("mikroE", 1, 7, 2); // Escribir la cadena


for (ii = 1; ii <= 10; ii++) // Dibujar los círculos
Glcd_Circle(63,32, 3*ii, 1);
delay2S();

Glcd_Box(12,20, 70,57, 2); // Dibujar la caja


delay2S();

/* Dibujar la cuarta imagen */


#ifdef COMPLETE_EXAMPLE
Glcd_Fill(0xFF); // Llenar el GLCD
Glcd_Set_Font(Character8x7, 8, 7, 32); // Cambiar de la fuente
someText = "8x7 Font";
Glcd_Write_Text(someText, 5, 0, 2); // Escribir la cadena
delay2S();
Glcd_Set_Font(System3x5, 3, 5, 32); // Cambiar de la fuente
someText = "3X5 CAPITALS ONLY";
Glcd_Write_Text(someText, 60, 2, 2); // Escribir la cadena
delay2S();
Glcd_Set_Font(font5x7, 5, 7, 32); // Cambiar de la fuente
someText = "5x7 Font";
Glcd_Write_Text(someText, 5, 4, 2); // Escribir la cadena
delay2S();
Glcd_Set_Font(FontSystem5x7_v2, 5, 7, 32); // Cambiar de la fuente
someText = "5x7 Font (v2)";
Glcd_Write_Text(someText, 5, 6, 2); // Escribir la cadena
delay2S();
#endif
}
}

truck_bmp.c:

/*Cabecera*****************************************************/

unsigned char const truck_bmp[1024] = {


0, 0, 0, 0, 0,248, 8, 8, 8, 8, 8, 8, 12, 12, 12, 12,
12, 10, 10, 10, 10, 10, 10, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9,137,137,137,137,137,137,
137,137,137,137,137,137,137, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 13,253, 13,195, 6,252, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
240,240,240,240,240,224,224,240,240,240,240,240,224,192,192,224,
240,240,240,240,240,224,192, 0, 0, 0,255,255,255,255,255,195,
195,195,195,195,195,195, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,255,240, 79,224,255, 96, 96, 96, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 64, 64, 64, 64,128, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
255,255,255,255,255, 0, 0, 0, 0,255,255,255,255,255, 0, 0,
0, 0,255,255,255,255,255, 0, 0, 0,255,255,255,255,255,129,
129,129,129,129,129,129,128, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0,255, 1,248, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 16,224, 24, 36,196, 70,130,130,133,217,102,112,
160,192, 96, 96, 32, 32,160,160,224,224,192, 64, 64,128,128,192,
64,128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 63, 96, 96, 96,224, 96, 96, 96, 96, 96, 96,
99, 99, 99, 99, 99, 96, 96, 96, 96, 99, 99, 99, 99, 99, 96, 96,
96, 96, 99, 99, 99, 99, 99, 96, 96, 96, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 96, 96, 96, 96, 96, 96, 96, 64, 64,
64,224,224,255,246, 1, 14, 6, 6, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2,130, 67,114, 62, 35, 16, 16, 0, 7, 3, 3, 2,
4, 4, 4, 4, 4, 4, 4, 28, 16, 16, 16, 17, 17, 9, 9, 41,
112, 32, 67, 5,240,126,174,128, 56, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1,127,127,127,127,255,255,247,251,123,191, 95, 93,125,189,
189, 63, 93, 89,177,115,243,229,207, 27, 63,119,255,207,191,255,
255,255,255,255,255,255,255,127,127,127,127,127,127,127,127,255,
255,255,127,127,125,120,120,120,120,120,248,120,120,120,120,120,
120,248,248,232,143, 0, 0, 0, 0, 0, 0, 0, 0,128,240,248,
120,188,220, 92,252, 28, 28, 60, 92, 92, 60,120,248,248, 96,192,
143,168,216,136, 49, 68, 72, 50,160, 96, 0, 0, 0, 0, 0, 0,
0, 0, 0,128,192,248,248,248,248,252,254,254,254,254,254,254,
254,254,254,254,254,255,255,255,255,255,246,239,208,246,174,173,
169,128,209,208,224,247,249,255,255,252,220,240,127,255,223,255,
255,255,255,255,255,254,254,255,255,255,255,255,255,255,254,255,
255,255,255,255,255,255,254,254,254,254,254,254,254,254,254,254,
254,254,254,254,255,255,255,255,255,255,254,255,190,255,255,253,
240,239,221,223,254,168,136,170,196,208,228,230,248,127,126,156,
223,226,242,242,242,242,242,177, 32, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 1, 1, 3, 3, 3, 7, 7, 7, 7, 7, 15,
15, 15, 7, 15, 15, 15, 7, 7, 15, 14, 15, 13, 15, 47, 43, 43,
43, 43, 43, 47,111,239,255,253,253,255,254,255,255,255,255,255,
191,191,239,239,239,191,255,191,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,127,127,127,127,255,255,191,191,191,191,255,254,
255,253,255,255,255,251,255,255,255,127,125, 63, 31, 31, 31, 31,
31, 31, 63, 15, 15, 7, 7, 3, 3, 3, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 1, 1, 1, 3, 3, 3, 11, 11, 11, 11, 7, 3, 14, 6, 6,
6, 2, 18, 19, 19, 3, 23, 21, 21, 17, 1, 19, 19, 3, 6, 6,
14, 15, 15, 7, 15, 15, 15, 11, 2, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

Para que este ejemplo funcione apropiadamente, es necesario marcar la


librería GLCD en la ventana Library Manager antes de compilar el programa.
Asimismo, es necesario incluir el documento truck_bmp.c en el proyecto.
4.17 EJEMPLO 15
Utilizar el panel táctil...

Un panel táctil es un panel fino, autoadhesivo y transparente, colocado sobre la


pantalla de un LCD gráfico. Es muy sensible a la presión así que un suave
toque provoca algunos cambios en la señal de salida. Hay diferentes tipos de
paneles táctiles. El más sencillo es el panel táctil resistivo del que se hablará
aquí.

Un panel táctil está compuesto por dos láminas rígidas, formando una
estructura de ‘sándwich’ que tiene capas resistivas en sus caras internas. La
resistencia de estas capas no excede normalmente de 1Kohm. Los lados
opuestos de las láminas disponen de los contactos para acceder a un cable
plano.

El procedimiento para determinar las coordenadas de la posición del panel que


ha sido presionado se puede dividir en dos pasos. El primero es determinación
de la coordenada X, y el segundo es de determinar la coordenada Y de la
posición.

Para determinar la coordenada X, es necesario conectar el contacto izquierdo


en la superficie A a la masa (tierra) y el contacto derecho a la fuente de
alimentación. Esto permite obtener un divisor de voltaje al presionar el panel
táctil. El valor de voltaje obtenido en el divisor se puede leer en el contacto
inferior de la superficie B. El voltaje variará en el rango de 0V al voltaje
suministrado por la fuente de alimentación y depende de la coordenada X. Si el
punto está próximo al contacto izquierdo de la superficie A, el voltaje estará
próximo a 0V.

Para la determinación de la coordenada Y, es necesario conectar el contacto


inferior de la superficie B a masa (tierra), mientras que el contacto superior se
conectará a la fuente de alimentación. En este caso, el voltaje se puede leer en
el contacto izquierdo de la superficie A .

Para conectar un panel táctil al microcontrolador es necesario crear un circuito


para el control del panel táctil. Por medio de este circuito, el microcontrolador
conecta los contactos adecuados del panel táctil a masa y a la voltaje de
alimentación (como describimos anteriormente) para determinar las
coordenadas X y Y. El contacto inferior de la superficie B y el contacto
izquierdo de la superficie A están conectados al convertidor A/D del
microcontrolador. Las coordenadas X e Y se determinan midiendo el voltaje en
los respectivos contactos. El software consiste en mostrar un menú en una
pantalla LCD gráfica, conmutar de encendido a apagado del panel táctil (control
del panel táctil) y leer los valores del convertidor A/D que representan
realmente las coordenadas X e Y de la posición.

Una vez determinadas las coordenadas, es posible decidir qué es lo que


deseamos que haga el microcontrolador. En este ejemplo se explica cómo
conmutar entre encendido y apagado dos pines digitales del microcontrolador,
conectados a los LEDs A y B.

En este ejemplo se utilizan las funciones que pertenecen a las librerías Glcd y
ADC.

Teniendo en cuenta que la superficie del panel táctil es ligeramente mayor que
la del LCD gráfico, en caso de requerir una mayor precisión en la determinación
de las coordenadas, es necesario incluir el software de calibración del panel
táctil.

/* Cabecera ***************************************************/

// Conexiones del módulo Glcd


char GLCD_DataPort at PORTD;
sbit GLCD_CS1 at RB0_bit;
sbit GLCD_CS2 at RB1_bit;
sbit GLCD_RS at RB2_bit;
sbit GLCD_RW at RB3_bit;
sbit GLCD_EN at RB4_bit;
sbit GLCD_RST at RB5_bit;
sbit GLCD_CS1_Direction at TRISB0_bit;
sbit GLCD_CS2_Direction at TRISB1_bit;
sbit GLCD_RS_Direction at TRISB2_bit;
sbit GLCD_RW_Direction at TRISB3_bit;
sbit GLCD_EN_Direction at TRISB4_bit;
sbit GLCD_RST_Direction at TRISB5_bit;
// Final de conexiones del módulo Glcd

// Declaración de la cadena a visualizar en el GLCD


char msg1[] = "TOUCHPANEL EXAMPLE";
char msg2[] = "MIKROELEKTRONIKA";
char msg3[] = "BUTTON1";
char msg4[] = "BUTTON2";
char msg5[] = "RC6 OFF";
char msg6[] = "RC7 OFF";
char msg7[] = "RC6 ON ";
char msg8[] = "RC7 ON ";

// Declaración de variables globales


long x_coord, y_coord, x_coord128, y_coord64; // almacenar la posición de
las
// coordenadas x e y

// Leer la coordenada X
unsigned int GetX() {
//reading X
PORTC.F0 = 1; // DRIVEA = 1 (electrodo izquierdo (LEFT) conectado,
electrodo
// derecho (RIGHT) conectado, electrodo superior
(TOP)desconectado)
PORTC.F1 = 0; // DRIVEB = 0 (electrodo inferior (BOTTOM)
desconectado)
Delay_ms(5);
return ADC_Read(0); // leer el valor de X de RA0(BOTTOM)
}

// Leer la coordenada Y
unsigned int GetY() {
//Leer la Y
PORTC.F0 = 0; // DRIVEA = 0 (electrodo izquierdo (LEFT)
desconectado, electrodo
// derecho (RIGHT) desconectado, electrodo superior (TOP)
conectado)
PORTC.F1 = 1; // DRIVEB = 1 (electrodo inferior (BOTTOM) conectado)
Delay_ms(5);
return ADC_Read(1); // leer el valor de Y de RA1 (del eléctrodo izquierdo
LEFT)
}

void main() {
PORTA = 0x00;
TRISA = 0x03; // RA0 y RA1 son entradas analógicas
ANSEL = 0x03;
ANSELH = 0; // Configurar otros pines AN como digitales de E/S
PORTC = 0 ; // Todos los pines del puerto PORTC están a 0 (incluyendo
los
// pines RC6 y RC7)

TRISC = 0 ; // PORTC es una salida

// Inicialización del GLCD


Glcd_Init(); // Glcd_Init_EP5
Glcd_Set_Font(FontSystem5x7_v2, 5, 7, 32); // Seleccionar el tamaño de
fuente 5x7
Glcd_Fill(0); // Borrar GLCD
Glcd_Write_Text(msg1,10,0,1);
Glcd_Write_Text(msg2,17,7,1);

// Visualizar botones en el GLCD:


Glcd_Rectangle(8,16,60,48,1);
Glcd_Rectangle(68,16,120,48,1);
Glcd_Box(10,18,58,46,1);
Glcd_Box(70,18,118,46,1);

// Visualizar los mensajes en los botones


Glcd_Write_Text(msg3,14,3,0);
Glcd_Write_Text(msg5,14,4,0);
Glcd_Write_Text(msg4,74,3,0);
Glcd_Write_Text(msg6,74,4,0);

while (1) {
// leer X-Y y convertirlo en la resolución de 128x64 píxeles
x_coord = GetX();
y_coord = GetY();
x_coord128 = (x_coord * 128) / 1024;
y_coord64 = 64 -((y_coord *64) / 1024);

//Si BUTTON1 ha sido presionado


if ((x_coord128 >= 10) && (x_coord128 <= 58) && (y_coord64 >= 18) &&
(y_coord64 <= 46)) {
if(PORTC.F6 == 0) { // Si RC6 = 0
PORTC.F6 = 1; // Invertir el estado lógico del pin RC6
Glcd_Write_Text(msg7,14,4,0); // Visualizar un nuevo mensaje: RC6 ON
}
else { // Si RC6 = 1
PORTC.F6 = 0; // Invertir el estado lógico del pin RC6
Glcd_Write_Text(msg5,14,4,0); // Visualizar un nuevo mensaje: RC6
OFF
}
}

// Si BUTTON2 ha sido presionado


if ((x_coord128 >= 70) && (x_coord128 <= 118) && (y_coord64 >= 18) &&
(y_coord64 <= 46)) {
if(PORTC.F7 == 0) { // Si RC7 = 0
PORTC.F7 = 1; // Invertir el estado lógico del pin RC7
Glcd_Write_Text(msg8,74,4,0); // Visualizar un nuevo mensaje: RC7 ON
}
else { // Si RC7 = 1
PORTC.F7 = 0; // Invertir el estado lógico del pin RC7
Glcd_Write_Text(msg6,74,4,0); // Visualizar un nuevo mensaje: RC7
OFF
}
}
Delay_ms(100);
}
}

Para que este ejemplo funcione apropiadamente, es necesario marcar las


siguientes librerías en la ventana Library Manager antes de compilar el
programa.
 GLCD
 ADC
 C_Stdlib
Apéndice A: Es hora de divertirse
Un microcontrolador se parece a un “genio en la botella”, y no se necesita
saber mucho para utilizarlo. Para crear un dispositivo controlado por un
microcontrolador, se necesita una PC, programa para compilar y un dispositivo
para transmitir el código de la PC al chip mismo. Aunque este proceso parece
muy lógico, con frecuencia surgen las dudas, no por que es complicado, sino
por un gran número de variaciones. A ver...
 A.1 VAMOS A EMPEZAR...
 A.2 COMPILACIÓN DE PROGRAMA
 A.3 PROGRAMAR EL MICROCONTROLADOR
 A.4 SISTEMAS DE DESARROLLO
VAMOS A EMPEZAR...

Los programas especiales en el entorno de Windows se utilizan para escribir un


programa para el microcontrolador. Este libro describe el programa
denominado mikroC PRO for PIC. La ventaja principal de este programa son
las herramientas adicionales instaladas para facilitar el proceso de desarrollo.

Si tiene experiencia en escribir programas, entonces sabe que se trata de


escribir todas las instrucciones en el orden en el que se deben ejecutar por el
microcontrolador y observar las reglas del lenguaje C. En otras palabras, sólo
tiene que seguir su idea al escribir el programa. ¡Esto es todo!
A.2 COMPILACIÓN DE PROGRAMA

El microcontrolador no entiende los lenguajes de alto nivel de programación, de


ahí que sea necesario compilar el programa en lenguaje máquina. Basta con
pulsar sólo una vez sobre el icono apropiado dentro del compilador para crear
un documento nuevo con extensión .hex. En realidad, es el mismo programa,
pero compilado en lenguaje máquina que el microcontrolador entiende
perfectamente. Este programa se le denomina con frecuencia un código hex y
forma una secuencia de números hexadecimales aparentemente sin
significado.
Una vez compilado, el programa se debe cargar en el chip. Usted necesita un
hardware apropiado para hacerlo posible - un programador.

PROGRAMAR EL MICROCONTROLADOR

Como hemos mencionado, para habilitar cargar un código hex en el


microcontrolador es necesario proporcionar un dispositivo especial,
denominado el programador, con software apropiado. Un gran número de
programas y circuitos electrónicos utilizados con este propósito se pueden
encontrar en Internet. El procedimiento es básicamente el mismo para todos
ellos y se parece a lo siguiente:

1. Coloque el microcontrolador en el zócalo apropiado del programador;


2. Utilice un cable adecuado para conectar el programador a una PC;
3. Abra el programa en código hex dentro de software del programador,
ajuste varios parámetros, y pulse sobre el icono para transmitir el código.
Pocos segundos después, una secuencia de ceros y unos se va a
programar en el microcontrolador.
Sólo ha quedado instalar el chip programado en el dispositivo destino. Si es
necesario hacer algunos cambios en el programa, el procedimiento anterior se
puede repetir un número ilimitado de veces.

A.3 ¿SERÁ UN FINAL FELIZ?

Esta sección describe en breve el uso del programa (compilador) mikroC PRO
for PIC y del software de programación (programador)PICflash. Todo es muy
simple...
Usted ya tiene instalado el mikroC PRO for PIC, ¿verdad? Al iniciarlo, abra un
proyecto nuevo y un documento nuevo con extensión .c dentro del mismo.
Escriba su programa...

OK. The program has been written and tested with the simulator. It did not
report any errors during the process of compiling into the hex code? It seems
that everything is under control...
De acuerdo. El programa ha sido escrito y probado con el simulador. ¿No ha
informado de ningún error durante el proceso de compilación en el código hex?
Parece que todo funciona perfecto...

El programa ha sido compilado con éxito. Sólo queda cargarlo en el


microcontrolador. Ahora necesita un programador que está compuesto por
software y hardware. Inicie el programa PICFlash.
La configuración es simple y no hacen falta explicaciones adicionales (tipo de
microcontrolador, frecuencia y reloj del oscilador etc.).

 Conecte la PC con el hardware del programador por un cable USB;


 Cargue el código hex utilizando el comando: File a Load HEX; y
 Pulse sobre el botón Write y espere...

¡Esto es todo! El microcontrolador está programado y todo está listo para su


funcionamiento. Si no está satisfecho, haga algunos cambios en el programa y
repita el procedimiento. ¿Hasta cuándo? Hasta quedar satisfecho...
A.4 SISTEMAS DE DESARROLLO

Un dispositivo que puede simular cualquier dispositivo en la fase de prueba, es


denominado un sistema de desarrollo. Aparte del programador, unidad de
alimentación, zócalo del microcontrolador, el sistema de desarrollo dispone de
los componentes para activar los pines de entrada y monitorear los pines de
salida. La versión más simple tiene cada pin conectado a su respectivo botón
de presión y un LED.

Una versión de calidad alta tiene los pines conectados a los visualizadores
LED, visualizadores LCD, sensores de temperatura u otros componentes por
los que puede estar compuesto un dispositivo destino. Si es necesario, todos
estos periféricos pueden estar conectados al microcontrolador por medio de los
puentes. Esto permite probar el programa entero en la práctica aún durante el
proceso de desarrollo, porque el microcontrolador no “sabe o no le interesa” si
su entrada está activada por un botón de presión o un sensor incorporado en
un dispositivo real.

Si dispone de un sistema de desarrollo, el proceso de programar y probar un


programa es aún más sencillo. Teniendo en cuenta que el compilador mikroC
PRO for PIC (en su PC) y el hardware del programador PICflash (en su sistema
de desarrollo) colaboran perfectamente, el proceso de compilar un programa y
programar el microcontrolador se lleva a cabo en un simple paso - al pulsar
sobre el icono Build and Program dentro del compilador. Desde este momento,
cualquier cambio en el programa afectará inmediatamente al funcionamiento de
alguno de los componentes del sistema de desarrollo.

¿Está de acuerdo con nosotros que es hora de divertirse?

Características principales del sistema de desarrollo EasyPIC6

1. Regulador de voltaje de alimentación


2. Conector USB para el programador en la placa
3. Programador USB 2.0 con soporte de mikroICD
4. Zócalo para el sensor de temperatura DS1820
5. Conector para el depurador externo (ICD2 o ICD3) de Microchip
6. Conector para la comunicación USB
7. Entradas de prueba del convertidor A/D
8. Conector PS/2
9. LCD 2x16 en la placa
10. Interruptores DIP permiten el funcionamiento de las resistencias pull-
up/pull-down
11. Puente para seleccionar las resistencias pull-up/pull-down
12. Conectores de los puertos E/S
13. Zócalo para colocar el microcontrolador PIC
14. Controlador del panel tácti
15. Extensor de puertos
16. Conector del LCD gráfico128x64
17. Potenciómetro de contraste del LCD gráfico
18. Conector de panel táctil
19. Teclado Menu
20. Teclado 4x4
21. Botones de presión para simular las entradas digitales
22. Puente para seleccionar el estado lógico de los botones de presión
23. Puente para poner en cortocircuito la resistencia de protección
24. Botón para reiniciar el microcontrolador
25. 36 diodos LED indican el estado lógico de los pines
26. Ajuste de contraste del LCD alfanumérico
27. Conector del LCD alfanumérico
28. Conector para la comunicación RS-232

Вам также может понравиться