Capitulo 2 Microcontroladores
Capitulo 2 Microcontroladores
Capitulo 2 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.
LENGUAJE ENSAMBLADOR
A pesar de todos los lados buenos, el lenguaje ensamblador tiene algunas desventajas:
Lenguaje C
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 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, el compilador 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 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.
Tipo de Tamaño
Descripción Rango de valores
dato (número de 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 ±1.17549435082·10-38 a
double 32
de doble precisión ±6.80564774407·1038
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.
Un entero es un número sin parte fraccionaria que puede estar expresado en los siguientes
formatos:
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
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:
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.
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.
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.
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.
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.
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:
Si hay varias variables con el mismo valor inicial asignado, el proceso se puede simplificar:
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:
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.
int Velocidad_de_ascensor
enum motor_de_ascensor {PARADA,INICIO,NORMAL,MÁXIMO};
Velocidad_de_ascensor = NORMAL; // Velocidad_de_ascensor = 2
La palabra clave typedef le permite crear con facilidad los nuevos tipos de datos.
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.
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
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:
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();
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
Operador Operació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
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
Operador
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 RELACIONALES
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.
Operador Función
&& Y
|| O
! NO
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:
Aparte de los operadores de asignación, dos operadores no deben estar escritos uno
junto al otro.
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.
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.
Como hemos mencionado, la otra forma combina tanto el operador if como el else:
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
Operador Switch
break;
case constante2:
break;
...
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:
BUCLES
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:
Bucle For
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.
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.
SENTENCIAS DE SALTO
SENTENCIA BREAK
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.
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
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.
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:
Para leer o modificar un elemento de matriz del ejemplo anterior, basta con introducir su
índice encerrado entre corchetes:
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
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.
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:
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 (*).
tipo_de_variable *puntero;
puntero = &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.
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;
..
};
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’.
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.
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:
..
.
};
Esto significa que f es una función que recibe un número real x como parámetro y devuelve
2*x-y.
Cada función debe ser declarada apropiadamente para poder interpretarla correctamente
durante el proceso de compilación. La declaración contiene los siguientes elementos:
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.
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:
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.
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ón main(). Un prototipo de función está compuesto por:
tipo de resultado
nombre de función
tipos de parámetros
un punto y coma (;)
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.
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.
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
}
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:
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
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]'.
Directivas 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.
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'.
También puede utilizar los parámetros para realizar substituciones más complejas:
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
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).
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
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:
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:
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.
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
...
}
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
}
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.
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.
Libraría Descripción
ANSI C Ctype
Utilizada principalmente para probar o para convertir los datos
Library
ANSI C Math
Utilizada para las operaciones matemáticas de punto flotante
Library
ANSI C Stdlib
Contiene las funciones de librerías estándar
Library
ANSI C String Utilizada para realizar las operaciones de cadenas y de manipulación
Library de memoria
- librerías misceláneas:
Librarí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 Utilizada para la implementación de funciones trigonométricas
Library fundamentales
Setjmp Library Utilizada para los saltos de programa
Librarí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
CANSPI Library
(MCP2515 o MCP2510)
Utilizada para las operaciones con las tarjetas de memoria
Compact Flash Library
Compact Flash
Utilizada para las operaciones con la memoria EEPROM
EEPROM Library
incorporada
EthernetPIC18FxxJ60 Utilizada para las operaciones con el módulo Ethernet
Library incorporado
Utilizada para las operaciones con la memoria Flash
Flash Memory Library
incorporada
Utilizada para las operaciones con el módulo LCD gráfico con
Graphic Lcd Library
resolución 128x64
Utilizada para las operaciones con el módulo de
I2C Library
comunicación serial I2C incorporado
Keypad Library Utilizada para las operaciones con el teclado (botones de
presión 4x4)
Utilizada para las operaciones con el LCD (de 2x16
Lcd Library
caracteres)
Utilizada para la comunicación utilizando el código
Manchester Code Library
Manchester
Utilizada para las operaciones con las tarjetas multimedia
Multi Media Card Library
MMC flash
Utilizada para las operaciones con los circuitos utilizando la
One Wire Library
comunicación serial One Wire
Utilizada para las operaciones con el extensor de puertos
Port Expander Library
MCP23S17
PS/2 Library Utilizada para las operaciones con el teclado estándar PS/2
Utilizada para las operaciones con el módulo PWM
PWM Library
incorporado
Utilizada para las operaciones con los módulos utilizando la
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
SPI Ethernet Library
ETHERNET (ENC28J60)
Utilizada para la comunicación SPI de 4 bits con el LCD
SPI Graphic Lcd Library
gráfico
Utilizada para la comunicación SPI de 4 bits con el LCD (de
SPI LCD Library
2x16 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
Utilizada para las operaciones con el módulo UART
UART Library
incorporado
Utilizada para las operaciones con el módulo USB
USB Hid Library
incorporado
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.
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 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...
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].
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...
Device (dispositivo):
Oscillator (oscilador):
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.
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 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.
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.
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
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.
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
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...).
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
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.