Documentos de Académico
Documentos de Profesional
Documentos de Cultura
El lenguaje C data del año 1972; fue creado por Dennis Ritchie en los laboratorios Bell como resultado de la
necesidad de reescribir los sistemas operativos UNIX con el fin de optimizar el conocido código ensamblador. El
lenguaje C fue la evolución de lenguajes previos llamados B creado por Ken Thompson, y BCPL. El nuevo lenguaje C,
rápidamente tomó fuerza por su funcionalidad y facilidad en la implementación en diversos sistemas
computacionales que requerían códigos de máquina. En 1983 el Instituto de Estándares Americanos estableció un
estándar que definiera al lenguaje C, conocido como ANSI C.
La forma del lenguaje C, se fundamenta en un complejo estructural que requiere un amplio estudio de las mismas,
sin embargo para la programación de los microcontroladores el estudiante requiere una porción fundamental que le
permita iniciar y crear los primeros proyectos en XC8.
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
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.
2.- CARACTERÍSTICAS PRINCIPALES DEL XC8
A continuación vamos a
presentar a los
elementos principales
del lenguaje XC8
desarrollado por
Microchip. 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 XC8 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 XC8.
3. Calcular temperatura; y
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 XC8 contienen por lo menos una función llamada main(void) 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(void) 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.
Todos los programas (código fuente) en XC8 tienen una estructura básica, a partir de la cual se desarrolla el
programa, como se muestra a continuación:
/*
* File: xxxxxxxxxxxxxx.c
* Author: xxxxxxxxxx
* Información del programa
* Created on xxxxxxxxxxxxx, hora xx:xx
*/
#include <stdlib.h> // Declara funciones para conversión numérica, asignación de memoria y otros
#include <stdio.h> // Archivo de la biblioteca estándar de Entrada/Salida
#include <xc.h> // Definición de registros y de sus bit’s del SFR
void main(void) {
---------------sentencias a ejecutar------
return;
}
Comentarios son usados para documentar un programa funcionalmente y para explicar que hace un bloque
particular o línea de código. Los comentarios son ignorados por el compilador. Existen dos formas de usar los
comentarios:
Comentario de Bloque /* este es un comentario de varias líneas */
Comentario de Bloque simple //este también es un comentario para una línea
El preprocesador acepta el código fuente como entrada y es responsable de:
•quitar los comentarios
•interpretar las directivas del preprocesador las cuales inician con #.
Por ejemplo:
#include -- incluye el contenido del archivo nombrado. Estos son usualmente llamados archivos de cabecera
(header). Por ejemplo:
#include <xc.h> -- Archivo con la definición de registros y bit’s del SFR.
#include <stdio.h> -- Archivo de la biblioteca estándar de Entrada/Salida.
#define -- define un nombre simbólico o constante. Por ejemplo:
#define TAM_MAX_ARREGLO 100
#include <nombre_fichero> o #include "nombre_fichero" como se mencionó es una directiva que hace que el
compilador incluya en el fichero fuente el texto que contiene el archivo especificado en <nombre_fichero>. Si el
nombre del fichero se incluye entre los símbolos < > el compilador busca el fichero en el directorio INCLUDE que
está en la carpeta de instalación XC8. Si se pone entre comillas dobles " " el compilador busca primero en el
directorio actual o directorio de trabajo donde hemos guardado nuestro proyecto y si no lo encuentra, entonces lo
busca en los directorios INCLUDE del compilador.
Haciendo la instalación en este orden, el compilador se debería agregar a MPLABX. Si no se instaló o se instaló al
revés no hay problema, el compilador se debe agregar manualmente.
Figura 1
Figura 2
Con estos pasos es posible agregar cualquier otro compilador que se quiera probar.
Figura 3
Terminado lo anterior lo primero que tenemos que hacer es
crear un proyecto en MPLABX. Para esto, se debe pulsar el
ícono New Proyect como se observa en la figura 3.
Figura 4
Para crear un proyecto desde cero, de las varias
opciones mostradas escogemos la Standalone
Project de la categoría Microchip Embedded .
Luego Next>
Figura 5
Figura 7
Figura 8
Figura 10
Figura 11
Figura 13
Figura 14
2.4 VARIABLES, IDENTIFICADORES, CONSTANTES Y TIPOS DE DATOS
La programación de PIC en C se puede comprender mejor si se estudian sus elementos básicos; una vez que se
dominen estos elementos se podrá dar solución a la gran mayoría de problemas de programación. El propósito de la
mayoría de los programas es resolver un problema. Los programas resuelven los problemas por medio de la
manipulación de información o datos. Normalmente los programas se caracterizan por permitir el ingreso de
información, tener uno o varios lugares de almacenamiento de dicha información, contar con las instrucciones para
manipular estos datos y obtener algún resultado del programa que sea útil para el usuario. También, las
instrucciones se pueden organizar de tal forma que algunas de ellas se ejecuten sólo cuando una condición
específica (o conjunto de condiciones) sea verdadera, otras instrucciones se repitan un cierto número de veces y
otras pueden ser agrupadas en bloques que se ejecutan en diferentes partes de un programa.
Una variable es un nombre que representa una o más localizaciones de memoria usadas para tener los datos. Una
variable puede ser entendida como un contenedor que puede almacenar datos usados en un programa. A una
variable también se le conoce como un identificador.
Ejemplos de identificadores válidos son los siguientes: a, b, tiempo, distancia1, caso_A, PI, velocidad_de_la_luz
Por el contrario, los siguientes nombres no son válidos: 1_valor, tiempo-total, dolares$, %final
En general es muy aconsejable elegir los nombres de las funciones y las variables de forma que permitan conocer a
simple vista qué tipo de variable o función representan, utilizando para ello tantos caracteres como sean necesarios.
Esto simplifica enormemente la tarea de programación y –sobre todo– de corrección y mantenimiento de los
programas.
Las variables definidas en un programa pueden ser de tipo LOCAL o GLOBAL. Las variables locales solo se utilizan en
la función donde se encuentra declarada. Las variables globales se pueden utilizar en todas las funciones del
programa. Ambas deben ser declaradas antes de ser utilizadas y las globales deben declararse antes de cualquier
función y fuera de ellas. Las variables globales son puestas a cero cuando se inicia la función principal.
En C, como en cualquier otro lenguaje, existen una serie de palabras clave (keywords) que el usuario no puede
utilizar como identificadores (nombres de variables, constantes y/o de funciones). A continuación se presenta la lista
de las 32 palabras clave del ANSI C, es importante evitarlas como identificadores:
CONSTANTES. Las variables pueden cambiar de valor a lo largo de la ejecución de un programa, o bien en
ejecuciones distintas de un mismo programa. Además de variables, un programa utiliza también constantes, es decir,
valores que siempre son los mismos. Un ejemplo típico es el número pi(π), que vale 3.141592654. Este valor, con
más o menos cifras significativas, puede aparecer muchas veces en las sentencias de un programa. En C existen
distintos tipos de constantes:
1. Constantes numéricas. Son valores numéricos, enteros o de punto flotante. Se permiten también constantes
octales (números enteros en base 8) y hexadecimales (base 16).
2. Constantes carácter. Cualquier carácter individual encerrado entre apóstrofos (tal como 'a', 'Y', ')', '+', etc.)
es considerado por C como una constante carácter, o en realidad como un número entero pequeño (entre 0
y 255, o entre -128 y 127, según los sistemas). Existe un código, llamado código ASCII, que establece una
equivalencia entre cada carácter y un valor numérico correspondiente.
3. Cadenas de caracteres. Un conjunto de caracteres alfanuméricos encerrados entre comillas es también un
tipo de constante del lenguaje C, como por ejemplo: "espacio", "Esto es una cadena de caracteres", etc.
4. Constantes simbólicas. Las constantes simbólicas tienen un nombre (identificador) y en esto se parecen a las
variables. Sin embargo, no pueden cambiar de valor a lo largo de la ejecución del programa. En C se pueden
definir mediante el preprocesador o por medio de la palabra clave const.
Tipos de datos en CX8. 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 CX8
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.
TABLE: INTEGER DATA TYPES
Type Size (bits) Arithmetic Type Valores que soporta
bit 1 Unsigned integer 0 ó 1
signed char 8 Signed integer -128 a 127
unsigned char 8 Unsigned integer 0 a 255
signed short 16 Signed integer -32768 a 32767
unsigned short 16 Unsigned integer 0 a 65535
signed int 16 Signed integer -32768 a 32767
unsigned int 16 Unsigned integer 0 a 65535
signed short long 24 Signed integer -8388608 a 8388607
unsigned short long 24 Unsigned integer 0 a 16777215
signed long 32 Signed integer -2147483648 a 2147483647
unsigned long 32 Unsigned integer 0 a 4294967295
signed long long 32 Signed integer
unsigned long long 32 Unsigned integer
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 long en CX8 equivale al tipo float.
En resumen, la declaración de variables se realiza indicando el tipo de variable seguido de un nombre que el
desarrollador asigna arbitrariamente a la variable. En el punto de la declaración de una variable es posible dar un
valor inicial a cada una de las variables sin embargo este último no es estrictamente necesario. Por último la
declaración debe culminar con el carácter punto y coma (;).
Las variables también pueden ser declaradas en un formato que asocia varias variables a un mismo nombre, este
formato se conoce como una cadena de variables, o un vector e incluso puede ser una matriz de variables, en
conclusión este tipo de declaraciones pueden ser de una o más dimensiones.
El siguiente ejemplo muestra un vector de caracteres, o también conocido como una cadena de caracteres:
char Texto[20]; //Cadena de caracteres con 20 posiciones de memoria.
De igual manera las cadenas de caracteres o de variables pueden ser declaradas con un valor inicial, este tipo
de declaración se puede ver en el siguiente ejemplo:
char Texto[20] = “Nuevo Texto”; //Declaración de una cadena de caracteres
//inicializada con el texto: Nuevo Texto.
int Enteros[5]={5,4,3,2,1}; //Declaración de una cadena de enteros con valores iniciales.
float Decimales[3]={0.8,1.5,5.8}; //Declaración de una cadena de números con
//punto decimal inicializadas.
Ejemplo 1: Para declarar una variable que pueda almacenar el nombre de una persona y que, inicialmente, contenga
el valor "Isabel", escribiremos: char nombre[7] = "Isabel";
Un dato de tipo cadena es un dato compuesto (estructurado), debido a que está formado por una agrupación de
caracteres.
Pues bien, dicha agrupación se define por medio de un array. Un array agrupa, bajo el mismo nombre de variable, a
una colección de elementos (datos) del mismo tipo.
Para declarar un array de caracteres, después del identificador de la variable, se tiene que escribir, entre corchetes
"[]", el número de caracteres que se van a almacenar en el array, más uno. Por tanto, en este caso, puesto que
"Isabel" tiene seis caracteres, hay que escribir un 7 entre los corchetes.
Se tiene que escribir un número más, porque en la memoria se va a reservar espacio
para los seis caracteres de la cadena "Isabel", más uno, conocido éste como el carácter
nulo, el cual se representa mediante una barra invertida y un cero (\0). El sistema se
encarga de "poner" dicho carácter, que indica el fin de la cadena.
Por consiguiente, en la memoria se almacenarán siete caracteres consecutivos, como
se observa en la figura.
Los caracteres del array pueden ser referenciados mediante el identificador del mismo,
seguido de un número entre corchetes. A dicho número, de manera formal, se le llama
"índice", y puede oscilar entre el valor 0 y n-1, siendo n el número de caracteres que
pueden ser almacenados en memoria en el array, en este caso 7.
<nombre_de_la_variable>[<índice>]
Por ejemplo, nombre[3] hace referencia al espacio de memoria donde está el carácter 'b'.
Fíjese que, en esta ocasión, el array de caracteres nombre ha sido inicializado con el valor
"June". De manera que, el fin de la cadena se encuentra en nombre[4], y no en el último
espacio de memoria reservado para el array, ya que, "June" tiene, solamente, cuatro
caracteres.
En este caso estamos desperdiciando espacios de memoria, lo que no es óptimo.
2.6 OPERADORES
Un operador es un carácter o grupo de caracteres que actúa sobre una, dos o más variables
para realizar una determinada operación con un determinado resultado. Ejemplos típicos de operadores son la suma
(+), la diferencia (-), el producto (*), etc. Los operadores pueden ser unarios, binarios y ternarios, según actúen sobre
uno, dos o tres operandos, respectivamente.
Los operadores aritméticos son los más sencillos de entender y de utilizar. Todos ellos son operadores binarios. En C
se utilizan los cinco operadores siguientes:
– Suma: +
– Resta: -
– Multiplicación: *
– División: /
– Resto: %
Todos estos operadores se pueden aplicar a constantes, variables y expresiones. El resultado es el que se obtiene de
aplicar la operación correspondiente entre los dos operandos. El único operador que requiere una explicación
adicional es el operador resto %. En realidad su nombre completo es resto de la división entera. Este operador se
aplica solamente a constantes, variables o expresiones de tipo int. Aclarado esto, su significado es evidente:
23%4 es 3, puesto que el resto de dividir 23 por 4 es 3. Si a%b es cero, a es múltiplo de b.
Ejemplos con las siguientes variables:
int A, B, C;
La suma aritmética entre dos o más números, se puede hacer como se ve en el siguiente ejemplo:
C = A+B; //Está expresión guarda la suma de A y B en la variable C.
C = A+B+C; //Está expresión guarda la suma de A, B y C en la variable C.
La resta aritmética entre dos o más números, se puede hacer como se ve en el siguiente ejemplo:
C = A-B; //Está expresión guarda la diferencia entre A y B en la variable C.
C = A-B-C; //Está expresión guarda la diferencia entre A, B y C en la variable C.
La operación matemática de multiplicación se puede realizar entre dos o más números, la operación se
relaciona con el carácter asterisco (*), está expresión se puede apreciar con mayor claridad en los siguientes
ejemplos:
C = A*B; //Está expresión guarda la multiplicación entre A y B en la variable C.
C = A*B*C; //Está expresión guarda la multiplicación entre A, B y C en la variable C.
La división aritmética en lenguaje C se especifica por medio de la barra inclinada (/), en el siguiente ejemplo
se puede observar su implementación:
C = A/B; //Está expresión guarda la división A entre B en la variable C.
La operación módulo calcula el módulo de una división aritmética es decir calcula el residuo de una división,
el cálculo del módulo se denota con el carácter de porcentaje, (%), la aplicación de está operación se puede
ver en el siguiente ejemplo:
C = A%B; //Está expresión guarda el residuo de la división de A entre B en la variable C.
Las operaciones aritméticas pueden ser usadas en forma combinada, es decir que se pueden mezclar varias
operaciones en una misma expresión, para ver esto con mayor claridad observe los siguientes ejemplos:
C = (A+B)/C; //Está expresión es equivalente a C = (A+B)÷C.
C = (A/B)*C; // Está expresión es equivalente a C = (A÷B) X C.
Como se verá más adelante, una expresión es un conjunto de variables y constantes –y también de otras expresiones
más sencillas– relacionadas mediante distintos operadores. Un ejemplo de expresión en la que intervienen
operadores aritméticos es el siguiente polinomio de grado 2 en la variable x:
5.0 + 3.0*x - x*x/2.0
Las expresiones pueden contener paréntesis (...) que agrupan a algunos de sus términos. Puede haber paréntesis
contenidos dentro de otros paréntesis. El significado de los paréntesis coincide con el habitual en las expresiones
matemáticas, con algunas características importantes que se verán más adelante. En ocasiones, la introducción de
espacios en blanco mejora la legibilidad de las expresiones.
Operadores de asignación. Los operadores de asignación atribuyen a una variable –es decir, depositan en la zona de
memoria correspondiente a dicha variable– el resultado de una expresión o el valor de otra variable (en realidad,
una variable es un caso particular de una expresión). El operador de asignación más utilizado es el operador de
igualdad (=), que no debe ser confundido con la igualdad matemática. Su forma general es:
nombre_de_variable = expresion;
Cuyo funcionamiento es como sigue: se evalúa expresion y el resultado se deposita en nombre_de_variable,
sustituyendo cualquier otro valor que hubiera en esa posición de memoria anteriormente. Una posible utilización de
este operador es como sigue:
variable = variable + 1;
Desde el punto de vista matemático este ejemplo no tiene sentido (¡Equivale a 0 = 1!), pero sí lo tiene considerando
que en realidad el operador de asignación (=) representa una sustitución; en efecto, se toma el valor de variable
contenido en la memoria, se le suma una unidad y el valor resultante vuelve a depositarse en memoria en la zona
correspondiente al identificador variable, sustituyendo al valor que había anteriormente. El resultado ha sido
incrementar el valor de variable en una unidad.
Así pues, una variable puede aparecer a la izquierda y a la derecha del operador (=). Sin embargo, a la izquierda del
operador de asignación (=) no puede haber nunca una expresión: tiene que ser necesariamente el nombre de una
variable. Es incorrecto, por tanto, escribir algo así como:
a + b = c; // incorrecto
Existen otros cuatro operadores de asignación (+=, -=, *= y /=) formados por los 4 operadores aritméticos seguidos
por el carácter de igualdad. Estos operadores simplifican algunas operaciones recurrentes sobre una misma variable.
Su forma general es:
variable op= expresion;
A continuación se presentan algunos ejemplos con estos operadores de asignación: distancia += 1; equivale a:
distancia = distancia + 1;
rango /= 2.0 equivale a: rango = rango /2.0
x *= 3.0 equivale a: x = x * 3.0
Operadores incrementales. Los operadores incrementales (++) y (--) son operadores unarios que incrementan o
disminuyen en una unidad el valor de la variable a la que afectan. Estos operadores pueden ir inmediatamente
delante o detrás de la variable. Si preceden a la variable, ésta es incrementada antes de que el valor de dicha
variable sea utilizado en la expresión en la que aparece. Si es la variable la que precede al operador, la variable es
incrementada después de ser utilizada en la expresión. A continuación se presenta un ejemplo de estos operadores:
i = 2;
j = 2;
m = i++; // despues de ejecutarse esta sentencia m=2 e i=3
n = ++j; // despues de ejecutarse esta sentencia n=3 y j=3
Estos operadores son muy utilizados. Es importante entender muy bien por qué los resultados m y n del ejemplo
anterior son diferentes.
Ejemplos para aclarar lo mencionado con las variables siguientes:
int A=100, B=10;
A++; //Este operador incrementa en una unidad el valor de A.
A--; //Este operador decrementa en una unidad el valor de A.
A+=4; //Este operador incrementa en 4 el valor de A.
A-=5; //Este operador decrementa en 5 el valor de A.
A/=4; //Este operador divide el valor de A en 4.
A*=3; //Este operador multiplica el valor de A por 3.
A+=B; //Este operador incrementa el valor de A en el valor de B unidades.
A*=B; //Este operador multiplica el valor de A por B veces.
Completamos el programa anterior con lo que se muestra a continuación que viene a ser el ejemplo primero1xc8:
/*
* File: primero1xc8.c
* Author: xxxxxxxxxx
* Programa de uso de tres variables con los operadores de asignación
* Created on xxxxxxxxxxxxx, hora xx:xx
*/
#include <stdlib.h> // Declara funciones para conversión numérica, asignación de memoria y otros
#include <stdio.h> // Archivo de la biblioteca estándar de Entrada/Salida
#include <xc.h> // Definición de registros y de sus bit’s del SFR
void main(void) {
Operadores relacionales. Este es un apartado especialmente importante para todas aquellas personas sin
experiencia en programación. Una característica imprescindible de cualquier lenguaje de programación es la de
considerar alternativas, esto es, la de proceder de un modo u otro según se cumplan o no ciertas condiciones. Los
operadores relacionales permiten estudiar si se cumplen o no esas condiciones. Así pues, estos operadores
producen un resultado u otro según se cumplan o no algunas condiciones que se verán a continuación.
En el lenguaje natural, existen varias palabras o formas de indicar si se cumple o no una determinada condición. En
inglés estas formas son (yes, no), (on, off), (true, false), etc. En Informática se ha hecho bastante general el utilizar la
última de las formas citadas: (true, false). Si una condición se cumple, el resultado es true; en caso contrario, el
resultado es false.
En C un 0 representa la condición de false, y cualquier número distinto de 0 equivale a la condición true. Cuando el
resultado de una expresión es true y hay que asignar un valor concreto distinto de cero, por defecto se toma un
valor unidad. Los operadores relacionales de C son los siguientes:
>(alt+62) Mayor que a>b
>= Mayor o igual que a>=b
< (alt+60) Menor que a<b
<= Menor o igual que a<=b
== Igual que a==b
!= No igual que a!=b
Todos los operadores relacionales son operadores binarios (tienen dos operandos), y su forma general es la
siguiente:
expresion1 op expresion2
Donde op es uno de los operadores (==, <, >, <=, >=, !=). El funcionamiento de estos operadores es el siguiente: se
evalúan expresion1 y expresion2, y se comparan los valores resultantes. Si la condición representada por el operador
relacional se cumple, el resultado es 1; si la condición no se cumple, el resultado es 0.
A continuación se incluyen algunos ejemplos de estos operadores aplicados a constantes:
(2==1); // resultado=0 porque la condición no se cumple
(3<=3); // resultado=1 porque la condición se cumple
(3<3) ; // resultado=0 porque la condición no se cumple
(1!=1); // resultado=0 porque la condición no se cumple
Operadores lógicos. Los operadores lógicos son operadores binarios que permiten combinar los resultados de los
operadores relacionales, comprobando que se cumplen simultáneamente varias condiciones, que se cumple una u
otra, etc. El lenguaje C tiene dos operadores lógicos: el operador Y (&&) y el operador O (||). En inglés son los
operadores and y or. Su forma general es la siguiente:
expresion1 || expresion2; // expresion1 OR expresion2
expresion1 && expresion2; // expresion1 AND expresion2
El operador && devuelve un 1 si tanto expresion1 como expresion2 son verdaderas (o distintas de 0), y 0 en caso
contrario, es decir si una de las dos expresiones o las dos son falsas (iguales a 0); por otra parte, el operador ||
devuelve 1 si al menos una de las expresiones es cierta. Es importante tener en cuenta que los compiladores de C
tratan de optimizar la ejecución de estas expresiones, lo cual puede tener a veces efectos no deseados. Por ejemplo:
para que el resultado del operador && sea verdadero, ambas expresiones tienen que ser verdaderas; si se evalúa
expresion1 y es falsa, ya no hace falta evaluar expresion2, y de hecho no se evalúa. Algo parecido pasa con el
operador ||: si expresion1 es verdadera, ya no hace falta evaluar expresion2. Los operadores && y || se pueden
combinar entre sí –quizás agrupados entre paréntesis–, dando a veces un código de más difícil interpretación. Por
ejemplo:
(2==1) || (-1==-1); // el resultado es 1
(2==2) && (3==-1); // el resultado es 0
((2==2) && (3==3)) || (4==0); // el resultado es 1
((6==6) || (8==0)) && ((5==5) && (3==2)); // el resultado es 0
Si A=10 y B=20:
(A==10)&&(B==20); // el resultado es ¿?
(A!=5)&&(B>2); // el resultado es ¿?
(A<=4)||(B>=6) ; // el resultado es ¿?
¡(A>3)&&(B!=4); // el resultado es ¿?
Operador negación lógica (!). Este operador devuelve un cero (false) si se aplica a un valor distinto de cero (true), y
devuelve un 1 (true) si se aplica a un valor cero (false). Su forma general es:
!expresion
p.e.: !(cont>max) || ((max==57) && (cont>=10))
a = 11100011
&(alt+38) Y lógico para manejo de bits c=a&b c = 11000000
b = 11001100
a = 11100011
|(alt+124) O lógico para manejo de bits c=a|b c = 11101111
b = 11001100
a = 11100011
^ EXOR lógico para manejo de bits c=a^b c = 00101111
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.
Otro Ejemplo: unsigned short a = 0b11010011,b= 0b01010001,c;
c=a&b; //c será 0b01010001
c=a|b; //c será 0b11010011
c=a^b; //c toma el valor 0b10000010
c= ~a; //c adopta el valor 0b00101100
c= a >> 3; //c: 0b00011010 entran 3 ceros por izq. y “pierde” los 3 salientes
c= b << 3; //c: 0b10001000 entran 3 ceros por dcha. y “pierde” los 3 salientes
La implementación de las operaciones lógicas NAND, NOR, XNOR, son similares a AND, OR, y XOR, agregando el
carácter de negación virgulilla, observe los siguieres ejemplos:
unsigned charVALOR1=0b01010000; //Variable inicializada en binario con el número 80.
unsigned char VALOR2=0b01011111; //Variable inicializada en binario con el número 95.
unsigned char RESULTADO;
RESULTADO = ~(VALOR1|VALOR2); //El valor de la operación lógica NOR , es guardado en RESULTADO.
RESULTADO = ~(VALOR1&VALOR2); //El valor de la operación lógica NAND, es guardado en RESULTADO.
RESULTADO = ~(VALOR1^VALOR2); //El valor de la operación lógica XNOR, es guardado en RESULTADO.
int Funcion ( void ) //Función con parámetro de entrada vacío y salida entera.
{ // Apertura de la función con corchete.
//Porción de código donde se ejecutan la rutina de la función.
} //Cierre de la función con corchete.
Los nombres que se designan a las funciones cumplen con las mismas reglas para nombrar las variables. El siguiente
ejemplo muestra una función que es capaz de calcular el producto de dos números con punto decimal:
Una función puede recurrir a otra función para realizar funciones más complejas, para demostrar está situación se
puede ver el siguiente ejemplo, que realiza el cálculo del área de una circunferencia en función de su radio:
Figura 2-1
En el caso en que una función requiera variables internas ésta declaración se debe hacer al principio de la misma,
antes de realizar cualquier otra acción o instrucción.
float Area_Circunferencia ( float Radio ) //Función para calcular el área del círculo.
{ // Apertura de la función con corchete.
float Area;
Area = Valor_PI()*Radio*Radio; //Cálculo del área del círculo.
return Area;
} //Cierre de la función con corchete.
Las funciones son creadas por el desarrollador, sin embargo existen algunas que están predefinidas en el compilador
y pueden ser consultadas e implementadas respetando los parámetros de entrada y salida, la más importante de
ellas es la función main o principal. La importancia de esta función radica en el hecho de que es sobre ella que corre
todo el programa del microcontrolador y ésta se ejecuta automáticamente cuando el microcontrolador se energiza.
La función main en el caso particular de los microcontroladores no contiene parámetros de entrada ni de salida, su
declaración se puede apreciar en el siguiente ejemplo:
Las declaraciones de funciones hechas por el desarrollador deben ser declaradas antes de la función main, y en el
caso de una función que invoque a otra función debe ser declarada antes de la función que hace el llamado.
Las funciones matemáticas trigonométricas y otras como logaritmos, exponenciales, y potenciación pueden ser
usadas con la librería predefinida por el compilador. Está librería se denomina C_Math.
if (expresión) if (expresión)
sentencia; {
sentencias
}
p.e. if ((x==y)&&(t>=20)||(z==12))
cuenta=0;
La sentencia if-else. Se evalúa una expresión y si es cierta, se ejecuta el primer bloque de código,
(o sentencia 1) si es falsa se ejecuta el segundo bloque (o sentencia 2):
Se pueden combinar varios if-else-if escalonada para establecer muchos caminos de decisión:
p.e. if (numero==1)
if (expresión 1)
y=x+1;
sentencia 1;
else if (numero==2)
else if (expresión 2)
y=x+2;
sentencia 2;
else if (numero==3)
else
y=x+3;
sentencia 3;
else
y>3);
Ejemplo 3:
/*
* File: primero3xc8.c
* Author: xxxxxx
* Uso de variables y estructura selectiva if y else
* Created on xxxxxxxxxxxxxxxxxxxx
*/
#include <xc.h>
#include <stdint.h> // define los distintos tipos de datos de forma precisa
//y sin importar la arquitectura del procesador/controlador
void main(void)
{
signed int a;
int b=4,c=2; // Declarar variables a, b y c
unsigned char r=0x0d,s,k;
linea16:
// Solo sentencia if
a = b-c; //
if ( a < 0)
{
b = 1;
c -= 1;
}
// Sentencia if-else
a =5*b;
b =40/b;
if ((a!=10)&&(b>20))
c = b-a;
else
c =b+a;
// Sentencia if-else-if
if (r==0x0F)
{
s=0x0F;
k=0x0F;
r--;
}
else if (r==0x0e)
{
s =13;
k=13;
r--;
}
else
{
s++;
k--;
r=r+2;
}
goto linea16;
return;
}
La sentencia switch. La sentencia switch brinda una forma más elegante de bifurcación múltiple. Podemos
considerarla como una forma más estructurada de la sentencia if – else – if escalonada, aunque tiene algunas
restricciones en las condiciones lógicas a evaluar, las cuales son comparaciones de valores enteros o caracteres.
Para elaborar el código en C se usan las palabras reservadas switch, case, break y default.
El diagrama de flujo de esta estructura sería el siguiente:
switch ( expresion )
{
case exprConst1 :
listaProp1
case exprConst2 :
listaProp2
case exprConstN :
listaPropN
default:
propDefault
}
El funcionamiento de esta estructura es como sigue:
1. Se evalúa la expresión que acompaña al switch. Esta expresión se
evalúa como expresión entera.
2. Se compara el valor obtenido, secuencialmente, con los valores que
acompañan los diferentes case, deteniéndose en el primero que coincida.
switch ( expresion )
{
case exprConst1 :
listaProp1
break;
case exprConst2 :
listaProp2
break;
case exprConstN :
listaPropN
break;
default:
propDefault
break;
}
Observe cómo tras la última sentencia de cada bloque aparece una sentencia break para dar por finalizada la
ejecución de la estructura selectiva múltiple (incluyendo el caso por defecto, default).
El caso por defecto (default) suele utilizarse para detectar caso no válido o erróneo, evitando que se produzcan
errores en la ejecución. El siguiente ejemplo que corresponde a primero4xc8 determina si un número de mes entre
el 1 y el 12, nos indique su correspondiente número de días:
/*
* File: primero4xc8.c
* Author: xxxxxxxxxxxxxxxxx
* Programa que lea un numero de mes (en el rango de 1 a 12)
* e indique el numero de dias del mes usando la estructura selectiva SWITCH.
* Created on xxxxxxxxxxxxxxxx
*/
#include <xc.h>
#include <stdint.h>
void main(void)
{
int mes = 0;
int ndias = 0;
linea16:
switch (mes=mes+1)
{
case 2:
ndias = 28;
break;
case 4:
case 6:
case 9:
case 11:
ndias = 30;
break;
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
ndias = 31;
break ;
default:
ndias = 111;
break ;
}
goto linea16;
return;
}
2.10 SENTENCIAS ITERATIVAS
Las sentencias de control iterativas sirven para que el programa ejecute una sentencia o un grupo de ellas un
número determinado o indeterminado de veces. Así es, esta sección no habla de otra cosa que de los bucles en C.
En este tipo de estructuras, se repite un conjunto de instrucciones en función de una condición. La principal
diferencia entre las diferentes estructuras repetitivas consiste en qué punto se realiza la comprobación de la
condición.
El lenguaje C soporta tres tipos de bucles, las cuales se construyen con las sentencias while, do – while y for. El
segundo es una variante del primero y el tercero es una versión más compacta e intuitiva del bucle while.
La sentencia while. Se emplea para repetir una sentencia o bloque de sentencias, se realiza la repetición mientras
sea cierta (no nula) una expresión (test). La expresión se evalúa antes de cualquier iteración, si es falsa ya no se
ejecuta la sentencia o bloque.
while (expresión)
{
sentencia (s);
}
Algunos aspectos a considerar:
/*
* File: primero5xc8.c
* Author: xxxxxxxxxxxx
* Programa que suma los 10 primeros números del 1 al 10 con while
* Created on xxxxxxxxxxxxxxxxxxxxxxx
*/
#include <xc.h>
#include <stdint.h>
void main(void)
{
int num = 0;
int suma = 0;
while (10 > num)
num ++;
suma += num;
return ;
}
Si lo compilamos y ejecutamos paso a paso, obtenemos el siguiente resultado: La suma hasta el 10 vale 10.
Como puede observarse, y en contra de lo que pudiera parecer a simple vista, el fragmento de código anterior no
suma los diez primeros números (del 1 al 10), sino únicamente el último (10); a pesar de que el compilador no ha
detectado ningún error. Eso es porque el bloque del while está formado únicamente por la sentencia num++;, que
es la que se repite 10 veces. Una vez finalizado el bucle while es cuando se ejecuta la sentencia suma += num;, que
lógicamente sólo se ejecuta una vez. La forma correcta será la siguiente:
/*
* File: primero5xc8.c
* Author: River
* Programa que suma los 10 primeros numeros del 1 al 10 con while
* Created on 26 de noviembre de 2016, 10:40 AM
*/
#include <xc.h>
#include <stdint.h>
void main(void) {
int num = 0;
int suma = 0;
while (10 > num)
{
num ++;
suma += num;
}
return;
}
Si lo compilamos y ejecutamos paso a paso, obtenemos el siguiente resultado: La suma hasta el 10 vale 55.
La sentencia do – while. Es una variación de la sentencia while simple. La principal diferencia es que la condición
lógica (expression) (test) de este bucle se presenta al final. Ahora las sentencias se ejecutan antes de que se evalúe
la expresión, por tanto el bucle se ejecuta siempre al menos una vez. Como se ve en la siguiente figura, esto implica
que el cuerpo o bloque (sentencia o grupo de sentencias) de este bucle se ejecutará al menos una vez. El diagrama
de flujo de esta sentencia sería:
do
{
sentencia (s);
}
while (expresión)
La sentencia for. Las dos sentencias anteriores, while y do – while, se suelen emplear cuando no se sabe de
antemano la cantidad de veces que se va a ejecutar el bucle. En los casos donde el bucle involucra alguna forma de
conteo finito es preferible emplear la sentencia for. El diagrama de ujo de esta sentencia sería:
En iniciación se le asigna un valor inicial
a una variable que se emplea para el
control de la repetición del bucle (hay
que declararla como variable), esa
inicialización se ejecuta una sola vez.
En una sentencia for puede omitirse cualquiera de los tres campos, pero es imprescindible mantener los ( ; ). Si se
omite el segundo campo, la condición se da por cierta, y se obtiene un bucle infinito.
Pueden introducirse varias sentencias tanto en el bloque iniciación como en el bloque incremento. En este caso las
sentencias van separadas por comas, ejecutándose de izquierda a derecha. En la siguiente línea después de la
sentencia for se coloca el punto y coma (;) para que incluya el bloque for.
int i, suma;
for ( suma = 0, i = 2; i <= 10; suma += i, i += 2)
;
Obsérvese el ( ; ) aislado que aparece tras la cláusula for. Esto es necesario porque una sentencia for siempre
incluye un bloque for. Si no hubiéramos incluido ese (;) (que representa una sentencia vacía), el bloque del for lo
hubiera constituido la sentencia que pueda continuar.
Complete el programa anterior con lo que se muestra a continuación y verifique su funcionamiento paso a paso para
la sentencia while, do-while y for.
/*
* File: primero5xc8.c
* Author:xxxxxxx
*1 Programa que suma los 10 primeros números del 1 al 10 con while
*2 Programa con operadores relacionales y lógicos con do-while
*3 Programa que obtiene la suma de los numeros pares
* comprendidos entre 2 y 10 con la sentencia for.
* Created on xxxxxxxxxxxxxxxxxxxxxxx
*/
#include <xc.h>
#include <stdint.h>
void main(void) {
return ;
}
Estructuras repetitivas anidadas. Una estructura puede contener otra dentro, cumpliendo que estén totalmente
anidadas:
for (......)
{
for (..........)
{
while (...)
{
...........
}
}
}
En bucles anidados cada alteración del bucle externo provoca la ejecución completa del bucle interno. Verifique el
funcionamiento de sentencias for anidados con el siguiente ejemplo que corresponde a primero6xc8:
/*
* File: primero6xc8.c
* Author: xxxxxxxx
* Programa anidados de la sentencia for
* Created on xxxxxxxxxxxxxxxxx
*/
#include <xc.h>
#include <stdint.h>
void main(void)
{
short COL; //Declaración de variables.
short FIL;
int x=0;
short MATRIZ[3][3]; //Declamación de un vector bidimensional.
for( COL=0; COL<3; COL++ ) //Declaración de ciclo for externo.
{
//Fragmento del primer ciclo.
for( FIL=0; FIL<3; FIL++ ) //Declaración de ciclo for interno anidado.
{
//Fragmento del ciclo anidado.
MATRIZ[COL][FIL] = COL*FIL;
x=MATRIZ[COL][FIL];
}
}
//Otro programa anidado
int num=0, i;
long fac;
for(num = 1; num <= 5; num++)
{
fac = 1;
for(i = 1; i <= num; i++)
fac = fac * i;
}
return;
}
Sentencia break.
Esta sentencia tiene dos usos posibles:
a).- Permite salir de un bucle de repetición en cualquier punto del bloque. Si se encuentra esta sentencia, el
programa pasa a la siguiente sentencia tras el bucle.Ejm:
k=12;
for (i=0; i<=25; i++)
{
if (i==k)
break; //”imprime” desde 0 hasta k=12
}
b).- Se puede utilizar para finalizar un “case” en una sentencia switch. Ejm:
case ‘M’:
dia=2;
break;
Sentencia continue.
Funciona de manera parecida a la sentencia break dentro de un bucle. Sin embargo, en lugar de forzar la terminación
del mismo, continue fuerza una nueva iteración del bucle y salta el código que hay hasta que se evalúa la condición
del bucle. Ejm:
char *cadena; //definimos puntero a cadena
int espacios;
…
for(espacios=0; *cadena; cadena++)
{
if (*cadena !=‘ ‘) continue;
//si carácter no es blanco sigue a la condición e incremento
espacios++;
//solo llega a incrementar espacios si era blanco el carácter
}
//Este fragmento cuenta por tanto los espacios en una cadena de caracteres
Sentencia return
Esta sentencia se utiliza para finalizar y volver desde una función hacia el punto en que se le llamó. Se puede
devolver un valor a la función que hizo la llamada:
return (expresión); o bien return expresión
Si se omite la expresión, el valor devuelto estaría indefinido (no hay error). Si no se necesita devolver ningún valor, lo
más correcto sería declarar el tipo void en la declaración del valor devuelto por la función.
Si no se incluye una sentencia return en una función, ésta se finaliza tras la ejecución de la última línea del código.
Ejm:
void no_hace_nada(int c)
{ c++;
return;
}
Sentencia goto
El uso del goto tiene muy mala imagen entre los programadores de alto nivel ya que contribuye a hacer ilegibles los
programas. En C es posible escribir los códigos sin su uso, sin embargo vamos a indicar su existencia y si alguien lo
necesita, que lo use si no puede evitarlo (lo usamos en ensamblador).
La sentencia goto necesita una etiqueta para operar, una etiqueta es cualquier identificador válido en C seguido de
dos puntos (:). La etiqueta debe estar en la misma función que el goto (¡ no se puede saltar entre funciones !)
Sintaxis goto etiqueta;
...
etiqueta: sentencia;
Ejm: bucle:
x++;
if (x<=100) goto bucle;
3 .- MICROCONTROLADORES PIC16F87XA
Bajo el nombre de esta subfamilia de microcontroladores, actualmente encontramos cuatro modelos: EL PIC
16F873A/4A/6A y 7A. Estos microcontroladores disponen de una memoria de programa FLASH de 4 a 8 KBytes de 14
bits, considerablemente superior frente al PIC 16F84A en el que solo disponíamos de 1Kbyte de 14 bits.
En la Tabla 1-1 se muestran las características comparativas más relevantes de esta familia de microcontroladores :
La familia consta de cuatro dispositivos (PIC16F873A, PIC16F874A, PIC16F876A y PIC16F877A). Los PIC
16F876A/873A entran en el bloque de dispositivos encapsulados en 28 pines y los PIC 16F877A/874A entran en el
bloque de dispositivos encapsulados en 40 pines. Los dispositivos de 28 pines no tienen implementado el puerto
paralelo esclavo.
Las siguientes figuras corresponden a los diagramas de bloque de los dispositivos. Los de 28 pines en la Figura 3
y los de 40 pines en la Figura 4.
En la Tabla 2 se muestra un listado de cada uno de los pines que corresponden a los dispositivos de 40 que tienen su
correspondiente patilla en los dispositivos de 28 pines.
Tabla 2 Breve descripción de pines para PIC de 28 y 40 pines
Muchas veces será necesario configurar los registros bit por bit, esto se hace en el XC8 mediante el corrimiento de
bits, lo que se comente aquí será aplicable a cualquier otro registro.
Por ejemplo, si se quiere utilizar solo el pin RB0 como salida, entonces el bit 0 de TRISB se tiene que poner a 0, esto
en C hay que hacerlo así:
TRISB &=~ (1<<0);
donde & es el operador de bits and y ~ es la negación, es lo mismo que hacer TRISB = TRISB & ~(0b00000001), aquí
se está diciendo que el bit 0 del registro TRISB se ponga a 0, con lo cual se logra que el pin RB0 sea una salida digital,
en el XC8 esto mismo se puede programar también así:
TRISBbits.TRISB0=0;
Si lo que se quiere por ejemplo es que sea el pin RB5 el que sea una salida se haría lo siguiente:
TRISB &=~ (1<<5); //es lo mismo que TRISB = TRISB & ~(0b00100000), también se puede hacer TRISBbits.TRISB5=0.
Si se quiere por ejemplo que los pines RB1, RB4 y RB7 sean salidas, esto en una sola línea sería así:
TRISB &=~ ((1<<1) | (1<<4) | (1<<7));
también se puede hacer así
TRISBbits.TRISB1=0;
TRISBbits.TRISB4=0;
TRISBbits.TRISB7=0;
Por ejemplo, si se quiere utilizar solo el pin RB0 como entrada, entonces el bit 0 de TRISB se tiene que poner a 1, esto
C hay que hacerlo así:
TRISB |= (1<<0);
donde | es el operador de bits or, es lo mismo que hacer TRISB = TRISB | (0b00000001);
de esta forma se asegura que el bit 0 del registro TRISB sea un 1, con lo cual se logra que el pin RB0 sea una
entrada digital, en el XC8 esto mismo se puede programar también así TRISBbits.TRISB0=1.
Si lo que se quiere por ejemplo es que sea el pin RB5 el que sea una entrada se haría lo siguiente
TRISB |= (1<<5); //es lo mismo que TRISB = TRISB | (0b00100000), también se puede hacer TRISBbits.TRISB5=1.
Si se quiere por ejemplo que los pines PB1, PB4 y PB7 sean entradas, esto en una sola linea sería así:
TRISB |= ((1<<1) | (1<<4) | (1<<7));
también se puede hacer así
TRISBbits.TRISB1=1;
TRISBbits.TRISB4=1;
TRISBbits.TRISB7=1;
Si se quiere que por los pines RB5 y RB2 salgan unos sería así:
PORTB |= ((1<<2)|(1<<5));
o también
PORTBbits.RB2=1;
PORTBbits.RB5=1;
Cuando algún pin o pines del microcontrolador PIC se han configurado como entradas digitales mediante los bits
del registro PORTx se pueden leer los estados de los pines del puerto, esto es, se puede leer si al pin del puerto x le
está llegando una alto o un bajo, se puede leer bit por bit o todo el puerto a la vez, para esto será necesario crear
variables donde se guardarán los estados leídos
.
Por ejemplo si todos los pines del puerto B son utilizados como entradas digitales se puede proceder así
char estados; //variable entera sin signo de 8 bits
estados=PORTB; //en la variable de 8 bits se guarda los estados de los pines del PUERTO B
Figura 3.6.1
Cuando los cambios de tensión cruzan la zona indefinida, generan cambios de alto a bajo y viceversa que el
microcontrolador detecta como un pulso. Para evitar el efecto del ruido o rebotes se debe realizar una pausa en
espera de la estabilidad del estado lógico. La duración promedio de un rebote es de 1 a 5 milisegundos, lo que indica
que se debe hacer un retardo superior a este tiempo para esperar la estabilidad. Un retardo adecuado para este
efecto es un tiempo mayor o igual a 10 milisegundos. Este retardo se debe aplicar después de detectar el primer
cambio sobre el pulsador.
La instalación de los pulsadores se puede hacer de dos formas, activo en alto o activo en bajo, en la siguiente figura
se puede apreciar la forma de configurar estás dos posibilidades:
Figura 3.6.2
La decisión de usar la activación en alto o en bajo, depende del
desarrollador quien debe analizar la forma de mayor simplicidad
en el momento de hacer el diseño.
Figura 3.6.3
El uso de los Dip-Switch es similar a los pulsadores y se puede configurar de la misma forma que un pulsador con
activación en alto o en bajo. Las configuraciones antes citadas se pueden apreciar en el siguiente circuito:
Figura 3.6.4
El programa siguiente segundo1xc8 lee el puerto A al que se le suma 0x2a y el resultado se muestra en el puerto B:
/*
* File: segundo1xc8.c
* Author: xxxxxxxxxxx
* PB<-PA+2Ah
* Created on 13 de julio de 2017, 11:00 AM
*/
#include <xc.h>
void main(void) {
// Configure the I/O ports
ADCON1 = 0x06;
TRISA = 0xFF;
TRISB = 0x00;
PORTA = 0;
while(1){
x = PORTA;
y=0x2a+x;
PORTB=y;
}
return;
}
LEER UN PULSADOR
Utilizando una estructura condicional “if” podemos leer un pulsador conectado en RB0 para encender o apagar un
LED en el pin RB4.
/*
* File: segundo1axc8
* Author: xxxxxxxxxxxxxxxxxxxxx
* Created xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
* Microcontrolador: PIC16F876A
* Control de un pulsador
*/
#include <xc.h> // Librería XC8
#define _XTAL_FREQ 4000000 // Frecuencia de reloj para el micro
Ejercicios:
1. Los datos que ingresan por el PA y PB se suman y el resultado se muestra en el PC.
2. Lo que ingresa por el PA se le resta a D5h y el resultado lo muestra por el PB.
3. Realizar un programa tal que compare un dato del PA con un número N de modo que:
a. Si (PA)=N -> El nible alto del PB en ON
b. Si (PA)>N -> Los bits pares del PB en ON
c. Si (PA)<N -> El nible bajo del PB en ON
4. Para cualquier dato del PA, en el PB los bits impares siempre en cero.
5. En el PB se vea siempre el inverso del PA.
6. En el PB siempre se tenga el intercambio de nibles del PA.
7. En el PB se obtenga la rotación de un bit a la izquierda de lo que ingresa por el PA.
8. Con un pulsador en RA0 se active en ON a un LED conectado al RB0, y con otro pulsador en RA1 se lo
desactive a OFF.
9. Con un pulsador en RA0 se ponga en ON a un LED conectado al RB0, y con el mismo pulsador se ponga en
OFF al LED.
Sabiendo que los tiempos en los micros son muy reducidos lo que vamos a hacer es crear una rutina que nos
multiplique el tiempo; para programar un tiempo o retardo en un programa con XC8 se hace de la siguiente forma:
#define _XTAL_FREQ 4000000 // se define la frecuencia del cristal para los retardos
/*==============================================================================
Inicializar puertos, esta función es llamada luego en el main().
==============================================================================*/
void initPorts(void)
{
TRISB = 0b00000000; // Todo el puerto B es salida
PORTB = 0; // Se apaga todo el puerto B
}
/*==============================================================================
Bucle principal
==============================================================================*/
void main(void)
{
// Inicializar puertos y periféricos
initPorts();
// Repetir indefinidamente
while(1)
{
// prender puerto B
PORTB = 0b11111111;
_delay(500000); //retardo de 1 millón de pulsos de reloj
// apagar puerto b
PORTB = 0b00000000;
_delay(500000);
}
return;
}
El siguiente programa tercero2xc8 es similar al anterior con la diferencia que tendrá un pulsador en RA1 como START
para iniciar el parpadeo y otro pulsador en RA2 como STOP para detener el parpadeo; pero al conectar la
alimentación en el PB se verá el número de meza.
/*
* File: StartStopPORTB.c
* Author: xxxxxxxxxxxxxxxxxxx
*
* Created on xxxxxxxxxxxxxxxxxxxx
*/
//StartStopPORTBxc8- pic 16f876a
#include <xc.h>
// CONFIG
#pragma config FOSC = XT // XT oscillator
#pragma config WDTE = OFF // WDT disabled
#pragma config PWRTE = ON // PWRT enabled
#pragma config BOREN = OFF // BOR disabled
#pragma config LVP = OFF // Single-Supply
#pragma config CPD = OFF // Data EEPROM off
#pragma config WRT = OFF // Flash protection off
#pragma config CP = OFF // Code protection off
#define _XTAL_FREQ 4000000
void main(void)
{
ADCON1=0x06;
TRISB=0x00;
TRISA=0xff;
PORTB=0;
PORTA=0;
while(1){
PORTB=0x0a;
if(PORTAbits.RA1==0)
{
while(1)
{
PORTB=0xaa;
__delay_ms(30);
PORTB=0x00;
__delay_ms(30);
if(PORTAbits.RA2==0)
break;
}
}
else
PORTB=0x0a;
}
return;
}
Ejercicios
1. Realizar un programa para el parpadeo de nibles con control de start y stop
a. Al conectar la alimentación
- LEDs del PB off
- Display del PC, mostrar el número de meza
b. Al pulsar START (RA1)
- LEDs del PB parpadeo de nibles 0x0F (625ms) y 0xF0 (275ms)
- Display del PC off
c. Al pulsar STOP (RA2)
Regresar al punto a.
2. Realizar un programa de comparación de lo ingresa por PD con un número N, con control de start y stop
a. Al conectar la alimentación
- LEDs del PB off
- Display del PC, mostrar el número de meza
b. Al pulsar START (RA1)
- Si (PD) = N -> En PB parpadeo de bits pares a f=5Hz
- Si (PD) > N -> En PB parpadeo de nibles a f=10Hz
- Si (PD) < N -> En PB parpadeo de bits impares a f=20Hz
- Display del PC off
c. Al pulsar STOP (RA2)
Regresar al punto a.
3. Realizar un programa tal que produzca una rotación de un bit a la derecha cada 150ms con control de start y
stop.
4. Realizar un programa para el control de un semáforo inteligente con control de start stop.
5. Realizar un programa para el control del nivel de un depósito de líquido. El cual consta de tres sensores,
vacío, lleno y rebose; con dos bombas para el llenado.
Las tablas en C son los ARREGLOS Y STRINGS: los arreglos pueden definirse como sucesiones de valores constantes ó
variables que son almacenados en memoria FLASH, o en RAM respectivamente. Los strings son arreglos formados de
caracteres ASCII, y terminados con un caracter "NULL".
const signed int web[5]={23,44,-81,92,-56}; es un arreglo de constantes y se almacenan en memoria FLASH. Su valor
no puede modificarse.
const char wob[30]="LAS CARACTERISTICAS MAS IMPORTANTES DEL DISPOSITIVO SON"; este arreglo se almacena en
la memoria FLASH. Las comillas indican al compilador que se trata de un string. Observe que no es necesario el uso
de corchetes.
signed int web[5]={23,44,-81,92,-56}; es un arreglo de variables inicializadas con un valor y se almacenan en RAM,
pero pueden modificarse a lo largo de un programa. Cada elemento del arreglo puede accesarse a través de su
subíndice, por ejemplo, en el arreglo "web": web[0]=23 , web[4]=-56
También puede declararse el arreglo sin inicializar sus valores. Por ejemplo:
signed int web[5]; //reserva espacio en memoria RAM para 5 variables.
/*
* File: display-contador-xc8.c
* Author: xxxxxxxxxxxx
*
* Created on xxxxxxxxxxxxxxxxxxxxxxxxxx
*/
//display-contador-xc8- pic 16f873a
#include <xc.h>
// CONFIG
#pragma config FOSC = XT // XT oscillator
#pragma config WDTE = OFF // WDT disabled
#pragma config PWRTE = ON // PWRT enabled
#pragma config BOREN = OFF // BOR disabled
#pragma config LVP = OFF // Single-Supply
#pragma config CPD = OFF // Data EEPROM off
#pragma config WRT = OFF // Flash protection off
#pragma config CP = OFF // Code protection off
#define _XTAL_FREQ 4000000
const unsigned char digitos[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
void main(void) {
unsigned char cont=0;
TRISB = 0;
PORTB = 0;
while(1)
{
PORTB=digitos[cont++];
if(cont==10)
cont=0;
__delay_ms(500);
}
return;
}
El siguiente programa cuarto2xc8 muestra 2 displays multiplexados para un contador desde 00 hsta 99.
/*
* File: cuarto2xc8.c
* Author: xxxxxxxxxxxxxxxxx
* 2 display con pic 16f876a
* Created on xxxxxxxxxxxxxxxxxxxxxxx
*/
#include <xc.h>
// CONFIG
#pragma config FOSC = XT // XT oscillator
#pragma config WDTE = OFF // WDT disabled
#pragma config PWRTE = ON // PWRT enabled
#pragma config BOREN = OFF // BOR disabled
#pragma config LVP = OFF // Single-Supply
#pragma config CPD = OFF // Data EEPROM off
#pragma config WRT = OFF // Flash protection off
#pragma config CP = OFF // Code protection off
d = numero / 10;
u = numero % 10;
PORTB = digitos[u];
PORTAbits.RA0 = 1;
__delay_ms(6);
PORTAbits.RA0 = 0;
PORTB = digitos[d];
PORTAbits.RA1 = 1;
__delay_ms(6);
PORTAbits.RA1 = 0;
}
void main(void) {
unsigned char cont = 0;
ADCON1 = 6;
TRISA = 0;
TRISB = 0;
PORTB = 0;
while (1) {
MostrarDigitos(numero); //Función de aprox. 40ms
conteo++;
if (conteo == 24) //40ms*12 aprox 500ms
{
numero++;
conteo = 0;
if (numero == 100)
numero = 0;
}
}
return;
}
Las pantallas LCD tienen una serie de pines, las cuales tienen unos nombres especiales que por supuesto tienen un
orden de conexión, son 14 pines ordenados del 1 al 14, esto siempre viene indicado en la placa de las pantallas lcd,
estos pines son para la alimentación, el control y la comunicación, el envió y recibo de datos, tienen ademas 2 pines
para el uso de un led interno que algunas pantallas lcd lo tienen y otras no, normalmente serán como se ve en
la siguiente imagen.
DDRAM
El módulo LCD posee una zona de memoria RAM llamada DDRAM (Data Display RAM) donde se almacenan los
caracteres que se van a mostrar en la pantalla. Tiene una capacidad de 80 bytes, 40 por cada línea, de los cuales sólo
32 se pueden visualizar a la vez (16 bytes por línea).
De las 80 posibles, las dos direcciones más importantes de la DDRAM son:
· Dirección 00h, que es el comienzo de la primera línea
· Dirección 40h, que el comienzo de la segunda línea
Modos de funcionamiento
El LCD tiene 3 modos de funcionamiento principales:
· Modo Comando
· Modo Carácter o Dato
· Modo de lectura del Busy Flag o LCD Ocupada
Inicialización
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:
El procedimiento de inicialización por el bus de datos de 4 bits:
Para conocer un poco mejor los comandos soportados por la librería para la pantalla lcd 16×2, es necesario
estudiar el juego de instrucciones soportadas por el chip controlador de pantalla, el cual se resume en la
siguiente tabla que hemos traducido de la hoja de datos. La mayoría de los comandos listados pueden ser
ejecutados llamando a una función en C de nuestra librería.
Código de Operación
Tiempo de
Instrucción Descripción
Ejecución
RS R/W B7 B6 B5 B4 B3 B2 B1 B0
Borra el contenido de la
pantalla y retorna el
Clear display 0 0 0 0 0 0 0 0 0 1 1.52 ms
cursor a la posición
“home” (dirección 0).
Retorna el cursor a la
Cursor home 0 0 0 0 0 0 0 0 1 * 1.52 ms
posición “Home”. Retorna
también el área de visión
a la posición inicial. El
contenido de la DDRAM
permanece intacto.
Incrementar/Decrementa
r dirección (I/D); Habilitar
Entry mode corrimiento de pantalla
0 0 0 0 0 0 0 1 I/D S 37 μs
set (S). Estas operaciones se
realizan al leer o escribir
datos
Enciende o apaga el
display (D), Cursor
Display on/off
0 0 0 0 0 0 1 D C B encendido / apagado (C), 37 μs
control
destello del cursor (blink)
(B).
Selecciona entre
desplazamiento de
pantalla o de cursor (S/C),
Cursor/displa
0 0 0 0 0 1 S/C R/L * * selecciona la dirección de 37 μs
y shift
desplazamiento (R/L). El
contenido de la DDRAM
permanece intacto.
Configurar en ancho de
bus (4 u 8 bits)
Function set 0 0 0 0 1 DL N F * * (DL),Numero de lineas de 37 μs
display (N), y tipo de
fuente (F).
Escribe la dirección de
CGRAM. Los datos a
Set CGRAM almacenar en CGRAM
0 0 0 1 CGRAM address 37 μs
address pueden ser enviados
después de esta
instrucción
Escribe la dirección de
DDRAM. Los datos a
Set DDRAM almacenar en DDRAM
0 0 1 DDRAM address 37 μs
address pueden ser enviados
después de esta
instrucción
La conexión más recomendable del display LCD 16x2 requiere 4 pines para los datos (D7:D4), 1 pin para
habilitar/deshabilitar el display (E) y 1 pin para los modos comando/carácter (RS).
Librería LCD
En xc8 hay que utilizar una librería para la comunicación con LCDs basados en el controlador HD44780 o
compatibles, a través de un interfaz de 4 bits para datos. Para el trabajo con el módulo LCD se debe añadir la
librería lcd_portd.h, que contiene las funciones listadas en la tabla siguiente.
FUNCION DESCRIPCION
lcd_Init() Inicializa el módulo LCD
lcd_Putch(char c) Visualiza un carácter en la posición actual del cursor
lcd_Putrs(char row, char column, const char * s) Visualiza texto en la posición especificada
lcd_Puts(char row, char column, char * s) Visualiza caracteres en la posición especificada
lcd_Clear(void) Limpia la pantalla y retorna a inicio
lcd_Cmd(char c) Envía un comando al LCD
El siguiente programa lcd1xc8 nos mostrará en el lcd la palabra HOLA, letra por letra en la primera línea.
/*
* File: lcd1xc8.c
* Author: xxxxxxxxxxxxxxxxxxxxxx
* pic 16f877a
* Created on xxxxxxxxxxxxxxxxxxx
*/
#include <xc.h>
#include "lcd_portd.h"
#define _XTAL_FREQ 4000000
// CONFIG
#pragma config FOSC = XT // Oscillator Selection bits (XT oscillator)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT enabled)
#pragma config BOREN = OFF // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming
#pragma config CPD = OFF // Data EEPROM Memory Code Protection bit off
#pragma config WRT = OFF // Flash Program Memory Write Enable bits off
#pragma config CP = OFF // Flash Program Memory Code Protection bit off
void main(void) {
lcd_Init();
while (1) {
lcd_Clear();
lcd_Putrs(1, 1, "H");
__delay_ms(500);
lcd_Putrs(1, 2, "O");
__delay_ms(500);
lcd_Putrs(1, 3, "L");
__delay_ms(500);
lcd_Putrs(1, 4, "A");
__delay_ms(500);
lcd_Clear();
__delay_ms(500);
return;
}
Figura 4.2.1
Los teclados matriciales pueden tener dimensiones mayores en función de la necesidad del desarrollador. Los
teclados especiales pueden ser fabricados con membranas plásticas con la cantidad de teclas y la distribución de las
mismas, de tal manera que suplan las necesidades del usuario. Sin embargo los teclados 4x4 permiten hacer una
interfaz lo suficientemente completa para muchas de las aplicaciones. Los teclados matriciales funcionan activando
una de 4 columnas y revisando cuál de las filas se activan, este proceso determina cual es la tecla pulsada, de igual
manera el análisis se puede hacer invirtiendo las columnas con las filas.
Digite, compile, simule y analice el siguiente programa:
/*
* File: teclado1xc8
* Author: xxxxxxxxxxxxx
*
* Created on xxxxxxxxxxxxxxxx
*/
#include <xc.h>
#define _XTAL_FREQ 4000000
#include "miconfig876a.h"
#include "keyriv.h"
char valor;
void main()
{
TRISA=0B11111111;
TRISC=0B11110000; //PUERTO C salida de la tecla presionada
PORTC=0;
key_init();
while (1)
{
valor=0;
while(valor==0)
{
valor=getkey(0);
}
//Funcion que te deja ver el canal analogo que hallamos elegido
switch(valor)
{
case '0': PORTC=0;
break;
case '1': PORTC=1;
break;
case '2': PORTC=2;
break;
case '3': PORTC=3;
break;
case '4': PORTC=4;
break;
case '5': PORTC=5;
break;
case '6': PORTC=6;
break;
case '7': PORTC=7;
break;
case '8': PORTC=8;
break;
case '9': PORTC=9;
break;
case 'A': PORTC=10;
break;
case 'B': PORTC=11;
break;
case 'C': PORTC=12;
break;
case 'D': PORTC=13;
break;
case '*': PORTC=14;
break;
case '#': PORTC=15;
break;
default: break;
}
}
return;
}
Digite, compile, simule y analice el siguiente programa que usa teclado y lcd:
/*
* File: teclado2lcd-xc8
* Author: xxxxxxxxxxxxxx
*
* Created on xxxxxxxxxxxxxxxxxxx
*/
#include <xc.h>
#include "miconfig876a.h"
#include "keyriv.h"
#include "lcd_portd.h"
#define _XTAL_FREQ 4000000
char valor,x;
void main()
{
lcd_Init();
key_init();
lcd_Clear();
lcd_Putrs(1, 1, "HOLA");
__delay_ms(500);
lcd_Putrs(2, 4, "TECLADO");
__delay_ms(500);
lcd_Clear();
lcd_Putrs(1, 1, "PULSA UNA TECLA");
__delay_ms(50);
while (1){
valor=0;
while(valor==0)
{
valor=getkey(0);
}
x=valor;
lcd_PutchDir(2,8,x);
__delay_ms(500);
}
return;