1. INTRODUCCION
La primera pregunta que deberíamos hacernos es ¿Por qué mutar a un lenguaje de alto nivel como el C y no seguir programando en en assembler?. Algunas de las razones posibles son:
El C es un lenguaje de alto nivel: esto implica que nos aisla hasta cierto punto del hardware independiente del tipo de microcontrolador que se esté esté programando.
Es más rápido y fácil de aprender que el assembler: Tiene una cantidad de sentencias reducidas y son las mismas independientes del microcontrolador. En cambio en assembler cada microcontrolador tiene su propio conjunto de instrucciones que varían en un número de 30 a más de 100 instrucciones que deberíamos aprender además de todos los modos de direccionamiento asociados.
Portabilidad: Si quisiera trasladar un programa en lenguaje C de un microcontrolador a otro (incluso de otra marca) lo podría hacer consiguiendo el compilador adecuado e introduciendo mínimos cambios. En cambio en assembler assembler tendría que aprender en nuevo conjunto de instrucciones del nuevo microcontrolador y rehacer completamente todo el programa programa
Flexibilidad y mantención: Es común que en los programas se tengan que hacer modificaciones y correcciones que debido a su organización estructurada del lenguaje C resultan programas más claros y más fácil de entender, a diferencia del assembler, que con el tiempo y con la extensión del programa se vuelve extremadamente difícil de entender incluso para el programador original del software.
Facilita el trabajo en equipo: Cuando los sistemas son complejos, se puede necesitar que varios programadores trabajen sobre el mismo. Al permitir el desarrollo de programas modulares se podría dividir el software en varios modulos, cada uno desarrollado por un programador programador diferente. diferente. Luego Luego el compilado compiladorr se encargaría encargaría de integrarlos integrarlos y compatib compatibilizar ilizar los recursos del microcontrolador con los recursos requerido por cada modulo individual.
Los programas en lenguaje C se desarrolla mucho más rápido que un programa en assembler
Mas fácil de entender por otros programadores diferente al programador original
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
La principal desventaja respecto al assembler es que el código en assembler puede llegar a resultar más eficiente y usar menos memoria de programa.
1.1 Definición de la Función Main Al comenzar a ejecutarse el programa dentro de un microcontrolador el Contador de Programa apunta a la primera posición de memoria y es allí donde busca la primera instrucción a ejecutar. Siendo que un programa en C es una agrupación de funciones que interactúan entre si, el compilador C necesitar saber cuál es la primera función a ejecutar. Esta función está definida por defecto y tiene el nombre de main().
void main(void) { // Instrucciones en C o llamada a funciones C }
Las llaves limitan el comienzo y el final de la función main() y de todas las funciones en C Todas la instrucciones deben finalizar con un punto y coma.
1.2 Comentarios. Comentarios. Come Comenta ntarr un
progr program amaa es una una práct práctica ica muy impo importa rtante nte para el mante mantenim nimien iento to y las
modificaciones futuras del mismo. Ya que con el tiempo hasta el propio programador olvida cual era el objetivo de una secuencia determinada de código. Existan dos formas en que se pueden introducir comentarios en un programa en C:
Con una doble doble barra invertida: // comentario
Barra con asterisco: /* Comentario */
1.3 Nombres de Identificadores
Los objetos objetos que utiliza el lenguaje C (funciones, (funciones, variables, variables, etc.) necesitan necesitan tener un nombre o identificador para poder ser referenciados. Las reglas respectos a los identificadores son: 565 6 PÁ POLITECNI GI CO NA N 2
Lenguaje C para Microcontroladores PIC
La principal desventaja respecto al assembler es que el código en assembler puede llegar a resultar más eficiente y usar menos memoria de programa.
1.1 Definición de la Función Main Al comenzar a ejecutarse el programa dentro de un microcontrolador el Contador de Programa apunta a la primera posición de memoria y es allí donde busca la primera instrucción a ejecutar. Siendo que un programa en C es una agrupación de funciones que interactúan entre si, el compilador C necesitar saber cuál es la primera función a ejecutar. Esta función está definida por defecto y tiene el nombre de main().
void main(void) { // Instrucciones en C o llamada a funciones C }
Las llaves limitan el comienzo y el final de la función main() y de todas las funciones en C Todas la instrucciones deben finalizar con un punto y coma.
1.2 Comentarios. Comentarios. Come Comenta ntarr un
progr program amaa es una una práct práctica ica muy impo importa rtante nte para el mante mantenim nimien iento to y las
modificaciones futuras del mismo. Ya que con el tiempo hasta el propio programador olvida cual era el objetivo de una secuencia determinada de código. Existan dos formas en que se pueden introducir comentarios en un programa en C:
Con una doble doble barra invertida: // comentario
Barra con asterisco: /* Comentario */
1.3 Nombres de Identificadores
Los objetos objetos que utiliza el lenguaje C (funciones, (funciones, variables, variables, etc.) necesitan necesitan tener un nombre o identificador para poder ser referenciados. Las reglas respectos a los identificadores son: 565 6 PÁ POLITECNI GI CO NA N 2
No pueden pueden tener tener símbolos símbolos gráficos a excepción excepción del del guion bajo ( _ )
No pueden pueden tener tener acentuación acentuación graficas
Un identificador no puede comenzar con un numero
Solo los primeros 32 caracteres son significativos
Diferencia entre mayúsculas y minúsculas
No puede puede ser ser una palabra palabra reservada reservada de de C o del compilador compilador
Ejemplos: contador, Contador, CONTADOR: son tres identificadores diferentes para C
Palabras reservadas del C:
1.4 Tipos de Datos
Todas las variables de C deben ser previamente definidas antes de poder ser usadas dentro del programa. programa. El tipo de datos que se le asigne a una variable le dice al compilador cual es la cantidad de memoria que tiene que reservar para esa variable, asi como el rango numérico máximo que puede representar. Esto le permite optimizar el usa de la memoria ram ya que que de esta forma no dispondrá mas byte byte de lo necesario, para esa variable en particular Exist Existen en distin distinto toss tipos tipos de forma formato to para para repre represe senta ntarr valo valore ress numé numéric ricos, os, dond dondee los los dos dos más distintivos son: representación en punto fijo y representación en punto flotante. El punto fijo se usa para representar valores enteros y representa valores consecutivos.
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
El punto flotante puede representar valores decimales ya que usa una porción de los bits para la mantisa y otra para los exponentes lo que le da la facilidad de representar números extremadamente grandes o extremadamente pequeños, aunque pierde continuidad entre los números.
1.5 Modificadores de Datos
Excepto para void los tipos básicos tienen modificadores que le preceden. Se usa un modificador para alterar el significado de un tipo base para encajar con las necesidades de diversas situaciones mas precisamente. Una lista de los modificadores es:
Signed Unsigned Long Short
Los modificadores signed y unsigned se usan para definir si la variable va a representar números solamente positivos o números positivos y negativos. La diferencias entre números con y sin signo es la manera en que el compilador interpreta el bit mas alto. Los números negativos se presentan utilizando el método de complemento a dos En cambio los modificadores long y short se refieren a la cantidad de memoria usada para representar esa variable.
565 6 PÁ GI N NA 2
POLITECNI CO
1.6 Declarando Variables
El formato general para la declaración de variables es: tipo lista_variables;
Ej:
int i, j, l; short int sf; unsigned int Contador = 0; double balance, ganancias, perdidas;
Observar que las variables pueden ser inicializadas en el momento de su declaración, como pasa con la variable Contador.
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
1.7 Regla de Alcance de las Variables
El lugar donde se declara una variable determina la visibilidad de la misma para las diversas partes del programa. Las reglas que determinan como se puede usar una variable basándose en el lugar del programa que fue declarada se llama regla de alcance. En los programas en C hay tres sitios donde se pueden declarar variables:
•
Fuera de todas las funciones, incluyendo la función main(). Se llaman globales
•
Dentro de una función. Se las denomina locales .
•
Dentro de la declaración de los parámetros formales de una función.
Las variables globales pueden ser accedidas (leídas y modificadas) por cualquier función que forma parte del programa. Las variables locales solo pueden ser accedidas dentro de la función en la cual fue declarada, por lo tanto es desconocida para el resto del programa. Por esta razón, dos variables locales definidas en dos funciones diferentes pueden tener el mismo nombre sin que por ello se origine algún conflicto. Sin embargo dos variables globales no pueden tener el mismo nombre. Otra diferencia importante es que las variables locales son creadas al entrar a la función y descartadas al salir de la misma. Por lo tanto el uso de variables locales optimiza el uso de la memoria ram. Cuando una variable usa el modificador static, su comportamiento es un hibrido entre una definición global y una local. Ya que la variable así definida, como sucede con la local, solo es conocida en el interior de la función que contiene su definición. Pero a igual que sucede con la variable global, en ningún momento es destruida y por lo tanto conserva su valor a lo largo del programa. O sea, es una variable local que conserva su valor entre llamadas a la función que la define. Ej:
565 6 PÁ GI N NA 2
POLITECNI CO
1.8 Modificadores de Tipo de Acceso
El lenguaje C posee dos tipos de modificadores que se usan para controlar la forma en que se pueden acceder o modificar las variables. Estos modificadores son llamados const y volatile Una variable definida como const no puede ser alterado el contenido de la variable dentro del programa, salvo para darle un valor inicial. Una variable definida como volatile le informa al compilador que se puede cambiar el valor de la variable de forma no explicita por el programa. Esto es lo que sucede con las posiciones de memoria ram asociadas a los registros especiales del microcontrolador, ya que su valor puede ser alterados por el hardware. Unos modificadores anexados por el MCC18 y que no corresponde al ANSIS C son: ram y rom que nos permiten determinar donde se guardan las constantes, si en memoria de datos o de
programa.
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
1.9 Constantes
Se refieren a los valores fijos que se le asignan a las variables, algunos ejemplos son:
Para formatos diferentes al decimal tenemos:
1.10 Operadores
El lenguaje C es muy rico en operadores, entendiéndose como operador a un símbolo que le dice al compilador que realice manipulaciones matemáticas, relacionales y lógicas y sobre bits. Operadores Aritméticos
565 6 PÁ GI N NA 2
POLITECNI CO
Los 4 primeros operadores no requieren mayor explicación, el operador “%” devuelve el resto de una división, así por ejemplo: 10 % 3 = 1(resto) Aunque este operador no se puede usar sobre los tipos float o double. El operador ++ añade 1 a su operando y el -- le resta 1. x++ es igual a x = x + 1 y--
es igual a y = y – 1
Estos operadores pueden preceder o seguir a su operando, pero esto implica una diferencia en el resultado obtenido. Así, por ejemplo: x = 10 y = ++x el resultado es y = 11
x = 10 y = x++ el resultado es y = 10
Operadores Relacionales y Lógicos
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Se refieren a las relaciones que pueden tener los valores unos con otros. La clave de los conceptos de operadores relacionales y lógicos es la idea de verdadero o falso. En C, cualquier valor distinto de cero es verdad , mientras que si el cero es falso. Las expresiones que usan operadores relacionales y lógicos devolverán 0 para falso y 1 para verdadero. Ejemplo:
If( (tecla == 0) &&(Contador ¡= 0 ) ) { LED = 1; }
Operadores Bit a Bit
Realizan operaciones lógicas, pero esta vez, bit por bit de las palabras que intervienen en la operación. Además se agregan 2 operadores más, que consisten en desplazar la palabra tanto bits a la derecha o a la izquierda, emulando las operaciones shift rigth y shift left que suelen formar parte del set de instrucciones de todos los microcontroladores. Ejemplo:
565 6 PÁ GI N NA 2
POLITECNI CO
Cuando el resultado de alguna de estas operaciones en general implica a uno de los operando, el C permite una notación simplificada como la siguiente:
1.11 Expresiones
Los operadores, constantes y variables constituyen las expresiones. Una expresión en C es cualquier combinación valida de estas piezas. Cuando se mezclan constantes y variables de diferentes tipos en una expresión, C las convierte al mismo tipo. El compilador C convertirá todos los operandos al tipo del operando más grande en una operación según la base de esta operación. Tal como describen en estas reglas de conversión de tipos: 1. Todos los char y short int se convierten a int. Todos los float a double . 2. Para todos los operandos, los siguientes ocurre en secuencia. Si uno de los operandos es un long double, el otro se convierte a long double . Si uno de los operandos es double, el otro se
convierte a double. Si uno de los opernados es long, el otro se convierte a long. Si uno de los operandos es unsigned, el otro se convierte a unsigned
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Después de que el compilador aplique estas reglas de conversión, cada par de operandos será del mismo tipo, y el resultado de la operación será del tipo de los operandos. Ejemplo:
Operador Cast
Se puede forzar una expresión a ser de un tipo especifico usando las construcción llamada cast. El formato general de un cast es: (tipo) expresión
Por ejemplo, si x es un entero y se quiere uno asegurar que la computadora evalúe la expresión x/2 como de tipo float para obtener componente fraccional, podríamos usar el operador cast de la siguiente forma:
(float) x/2
Aquí el cast (float) se asocia con x, que provoca que el compilador transforme a x de entero a float y como vimos antes, el resultado también será float.
565Sin embargo, hay que ser cuidadoso ya que si la expresión fuera: 6 PÁ POLITECNI GI CO N NA 2
(float) (x/2)
el compilador haría primero la división entera y luego, ese resultado entero, lo pasaría el tipo float.
Otro ejemplo: int z; int x = 150; char y = 63;
z = (y * 10) + x
¿Qué resultado da? ¿Cómo se puede solucionar?
Este ejemplo pone de manifiesto uno de los inconvenientes que se presentan al existir una expresión con varios operadores, que se refiere en qué orden y con qué prioridad se aplican los mismos. Esto se resuelve en la reglas de precedencia.
Precedencia de los operandos
Los operandos tienen la siguiente precedencia:
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
El compilador evalúa de izquierda a derecha los operadores con el mismo nivel de precedencia.
Ejemplo:
y=3+2*4
// y = 11
y = (3 + 2) * 4 // y = 20 y = 4 >> 2 * 3 // y = 4 >> 6 y = 3 * 2 >> 4 // y = 6 >> 4
565 6 PÁ GI N NA 2
POLITECNI CO
SENTENCIAS DE CONTROL DE PROGRAMA
Las sentencias de control de programa son la esencia de cualquier lenguaje de computadora, ya que gobiernan el flujo de la ejecución del programa. La sentenciad de control del lenguaje C son ricas y poderosas y ayudan a explicar la popularidad del lenguaje. Se pueden dividir las sentencias de control de programa en tres categorías. La primera consta de unas instrucciones condicionales if y switch. La segunda son las sentencias de control del buche while, for y do-while. La tercera catergoria contiene la instrucción de bifurcación incondicional goto.
a.
La Sentencia if
La forma general de la sentencia if es: if (condición) sentencia_1; else sentencia_2;
o if (condición) {
sentencias_1; } else {
sentencias_2; }
La clausula else es opcional. Si es verdad la condición (distinta de cero),
se
ejecutara
la
sentencia o bloque que forma el objetivo de if ; de lo contrario, si existe else, se ejecutara la sentencia o bloque que es el objetivo de else. O sea que solo el código asociado con if o el asociado con else se ejecutara, nunca ambos.
El escalonador de if-else-if
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Una construcción corriente en programación es el escalonador if-else-if . Que se verá como:
if (condición)
sentencia; else if (condición)
sentencia; else if (condición)
. . . . else
sentencia;
El compilador evalúa las expresiones condicionales desde arriba descendiendo, tan pronto como la computadora encuentra una condición verdadera, ejecuta la sentencia o bloque de sentencias asociada y salta el resto de la escalera. Si no son verdad ninguna condición, se ejecutara el else final. Si no hay else final y son falsas todas las condiciones, no se hace nada.
La expresión condicional
Dentro de la expresión condicional del if , se puede usar cualquier expresión valida de C, no se necesita restringir el tipo de expresiones a solo las que invocan operadores relacionales y lógicos. Todo lo que se requiere es que la expresión evaluada de cero o no cero.
b.
La Sentencia switch 565 6 PÁ GI N NA 2
POLITECNI CO
Aunque el escalonador de if-else-if puede realizar comprobaciones múltiples, el código puede ser difícil de seguir y puede confundir al programador. Por esta razones, el C tiene incorporada una sentencia de bifurcación múltiple llamada switch. En él, el programa comprueba una variable sucesivamente frente a una lista de constantes enteras o de carácter. Después de encontrar una coincidencia, se ejecuta la sentencia o bloque de sentencias que se asocian con la constante. La forma general de la sentencia switch es:
switch(variable){ case constante1:
secuencia de sentencias; break ; case constante2:
secuencia de sentencias; break ;
. . . . default:
secuencia de sentencias; }
Donde se ejecutará la sentencia default si no coincide ninguna. El default es opcional; si no está presente, no se hace nada. Cuando se encuentra una coincidencia, el programa ejecuta las sentencias asociadas con el case hasta encontrar un break o en el caso del default (o el último case, si no está presente el default) el final de la sentencia switch. Hay dos cosas importantes que saber sobre la sentencia switch:
1. La sentencia switch solo puede comprobar por igualdad, mientras que la if puede ser de cualquier tipo.
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
2. No pueden tener dos constantes case valores idénticos en el mismo switch, aunque un switch puede contener a otro switch con constantes de case iguales.
La sentencia switch es más eficiente y es más clara de leer que el escalonador if-else-if
La sentencia break
Las sentencias break son opcionales dentro de la sentencia switch. Se usan las sentencias break para terminar la secuencia asociada con cada constante. Si se omite esta sentencia, la ejecución continuará en las sentencias del siguiente case hasta que se encuentre un break o el final del switch. Ejemplo:
switch(t){ case 1: sentencias1; break; case 2: sentencias2; break; case 3: sentencias3; case 4: case 5: sentencias5; break; case 10: sentencias10; break; } De este ejemplo podemos hacer varias observaciones: 565 6 PÁ GI N NA 2
POLITECNI CO
1. Las constantes asociadas a los case, no tienen que ser números consecutivos, en este ejemplo las constantes 6, 7, 8, 9, no existen, y por lo tanto si t asume alguno de estos valores no se ejecuta nada porque no está implementada la opción default. 2. Las sentencias5 tienen asociadas dos constantes: 4 y 5, si t asume cualquiera de estos dos valores se ejecutarán estas sentencias. 3. Si t asume el valor 3 se ejecutaran las sentencias3, pero al no contener un break , seguirá con las sentencias5 hasta encontrar el próximo break.
c.
Bucles
Los bucles permiten repetir un conjunto de instrucciones hasta que se cumple cierta condición. Los bucles de C son el for, el while y do-while.
2.3.1 El bucles for
El formato general de la sentencia for es:
for(inicialización; condición; incremento) sentencia;
En su forma más simple, la inicialización es un sentencia de asignación que el compilador usa para establecer la variable de control del bucle. La condición es una expresión que comprueba la variable de control del bucle cada vez para determinar cuándo salir del bucle. El incremento define la manera en que cambia la variable de control del bucle cada vez que se repite el mismo. Se deben separar estas tres grandes secciones usando punto y coma. El bucle for continuará la ejecución mientras la condición sea verdad. Una vez que es falsa, la ejecución del programa, continuará en la sentencia que sigue al for. Ejemplo vamos a sumar los números naturales del 1 al 100:
Resultado = 1 + 2 + 3 + 4 + 5 + …..+ 99 + 100
void main(void)
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
{ char i; int Res = 0;
for(i = 1; i <= 100; i++) Res = Res + i; }
Esto va a dar como resultado que la suma se repita 100 veces. No es necesario que la variable se tenga que incrementar, obtendríamos el mismo resultado si usáramos la siguiente sentencia:
for(i = 100; i > 0; i--) Res = Res + i;
El incremento podría ser cualquier expresión válida de C como: i = i + 5 El bucle for a igual que las sentencias anteriormente vistas, se puede aplicar a un conjunto de sentencias, encerrando las mismas entre corchetes.
El bucle infinito
Uno de los usos mas interesante para el bucle for es crear un bucle infinito. Debido a que ninguna de las tres expresiones que forman el bucle for se dan, se puede hacer un bucle sin fin teniendo la expresión condicional vacía.
for( ; ; ;)
{ sentencias; }
2.3.2 565El bucles while 6 PÁ POLITECNI GI CO N NA 2
El formato general es:
while(condición) sentencia;
donde sentencias, puede ser una sentencia vacía, una sentencia única o un bloque de setencias que se repetirán. La condición puede ser cualquier condición válida. El bucle itera mientras la condición sea verdad. Cuando llega a ser falsa, el control del programa pasa a la línea que sigue al bucle. Como en el bucle for, el bucle while comprueba la condición en lo alto del bucle, lo que significa que el código del mismo no se ejecutara siempre. Debido a que while realiza la prueba en lo alto, es bueno para situaciones donde no se quiera ejecutar el bucle. La posición de la prueba elimina la necesidad de realizar una prueba condiciona separada antes del bucle.
2.3.3 El bucles do/while
Al contrario del los buches for y while que comprueban la condición en lo alto del bucle, el bucle do/while la examinan en la parte baja del mismo. Esta característica provoca que un bucle do/while siempre se ejecutara al menos un vez. La forma general del bucle do/while es:
do{ sentencias; } while(condición);
2.4 Anidamiento de Bucles
Cuando un bucle está dentro del otro, el bucle más interno se dice que está anidado. El anidamiento de bucles permite resolver en forma compacta algunos problemas complejos.
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
2.5 Rompiendo Bucles
La sentencia break tiene dos usos. El primer uso es terminar un case en la sentencia switch. El segundo es forzar la terminación inmediata de un bucle, saltando la prueba condicional normal del bucle. Cuando se encuentra la sentencia break en un bucle, el programa termina inmediatamente el bucle y el control del programa va a la sentencia siguiente al bucle. Ej:
for(t=0; t< 100; t++) { U = t + U; if( t == 5 ) break; } ………; ………
En este programa se obtiene solo la suma de los 5 primeros números, ya que la sentencia break se ejecuta en la sexta iteración y corta el bucle.
2.6 La sentencia continue
La sentencia continue funciona de manera similar a la sentencia break . Sin embargo a diferencia de break que rompe el bucle, continue fuerza la siguiente iteración y salta cualquier código entre medias. Ej: for(x = 0; x<100; x++) { if(x%2) continue; y = x ^ 2 + y; 565} 6 PÁ GI N NA 2
POLITECNI CO
Cada vez que “x” asume un valor impar, se ejecuta la sentencia continue que fuerza la próxima iteración y salta el cálculo de la variable “y”. En los bucles while y do/while, una sentencia continue provoca que el control del programa vaya directamente a la prueba condicional y después continué el proceso del bucle.
2.7 Etiquetas y goto
La sentencia goto requiere un etiqueta para poder funcionar. Una etiqueta es un identificador válido de C que se sigue por dos puntos. Aún más, la etiqueta debe estar en la misma función que usa el goto. Por ejemplo, se podría escribir un bucle de 1 a 100 usando goto y una etiqueta, como se ve aquí:
x = 1; Loop: x++; if(x<100) goto Loop;
Un buen momento para usar goto es cuando se requiere salir de varias capas de anidamiento
for(…) { for(….) { while(….){ if(…)goto Stop; ……….. ………. } } } Stop: …….;
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
El uso excesivo de la sentencia goto hace al programa confuso e ilegible, mas cuando en el C se dispone de muchas y versátiles estructuras que pueden reemplazar sin problema su funcionalidad. Por lo que no es aconsejable su uso.
565 6 PÁ GI NA N 2
POLITECNI CO
ARRAY Y CADENAS
Un array es una colección de variables del mismo tipo que se referencia utilizando un nombre común. El compilador los coloca en posiciones contiguas de memoria. Un array puede tener una o varias dimensiones. Para acceder a un elemento específico dentro del array se usa un índice o varios índices, si es multidimensional.
3.1 Array Unidimensional
El formato general para la declaración de un array unidimensional es:
tipo nombre_array[tam];
Ej:
int BufferRx[16];
Aquí, “tipo” declara el tipo base del array. El tipo base determina el tipo de datos de cada elemento del array. El “tam” define cuantos elementos guardará. En nuestro ejemplo, es un array de tipo entero signado, llamado de “BufferRx” de 16 elementos. En C, todos los arrays usan cero como índice del primer elemento. Por tanto BufferRx[0] es el primer elemento elemento del array y BufferRx[1 BufferRx[15] 5] es el último último elemento elemento del mismo. mismo. Los array array son muy comunes comunes en progra programaci mación ón porque porque permite permiten n tratar tratar fácilme fácilmente nte muchas muchas variables relacionadas bajo un mismo nombre. Por ejemplo, podríamos poner en un array los datos que vayamos recibiendo del puerto serie ( comunicación RS-232), para luego ser procesados en otra parte del código. Otro uso bastante común es crear un array para los datos leidos de los conversores A/D, ya que en general se suelen promediar un grupo de muestras antes de darla como una lectura válida, disminuyendo de esta forma el ruido inherente a este tipo de medición. Por ejemplo:
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Aquí se ve, como definimos un array para contener 16 mediciones seguidas de temperatura para luego promediarlas en un único valor. La cantidad de bytes que ocupa un array depende del tamaño del mismo y el tipo de dato guardado, en este último ejemplo el array ocupa 32 bytes.
3.1.1 No comprobación de Contornos
El lenguaje C no realiza comprobación de contornos en los arrays: así no detiene la escritura fuera del final del array, si el índice sobrepasa sobrepasa el máximo valor valor del mismo. mismo. Esto trae aparejado aparejado que se sobrescriban sobrescriban las posiciones posiciones de ram siguientes siguientes a las ocupadas ocupadas por el array, alterando el valor valor de la variable que ocupa dicha posición. Este tipo de error es muy difícil de descubrir, por lo que, será resp respon onsa sabil bilida idad d del del progr program amad ador or hacer hacer la comp compro roba bacio cione ness de cont contorn orno o cuan cuando do las cons conside idere re necesarias.
3.2 Cadenas
En C una cadena no es más que un array unidimensional unidimensional de caracteres que termina en un nulo o cero. cero. Los compone componentes ntes de una cadena cadena (las letras), letras), son represent representada adass numéric numéricame amente nte a través través del código 565 ASCII, por lo que cada letra es guardada en un byte de acuerdo a este código. 6 PÁ POLITECNI GI CO NA N 2
Una cadena se puede inicializar en C18 de la siguiente manera: const rom char Mensaje [10] = { “Curso C18”}
Donde el tamaño del array debe tener en cuenta no solo todos los caracteres, incluido los espacios en blanco, sino también un espacio adicional para el terminador nulo. Los valores realmente guardados en este array serán: Mensaje[0] = 67 (C) Mensaje[1] = 117 (u) Mensaje[2] = 114 (r) Mensaje[3] = 115 (s) Mensaje[4] = 111 (o) Mensaje[5] = 32 ( ) Mensaje[6] = 67 (C) Mensaje[7] = 49 (1) Mensaje[8] = 56 (8) Mensaje[9] = 0 (/0)
3.3 Array Bidimensionales
C permite array multidimensionales. La forma mas sencilla de array multidimensional es la de dos dimensiones. En esencia, un array bidimensional es un array de arrays unidimensionales. Para declarar un array de enteros bidimensionales “dosd” de tamaño 10,20 se escribirá:
int dosd[10][20];
Recordar que a igual que pasaba con lo array unidimensional el primer elemento es dosd[0][0] y el último dosd[9][19].
Veamos un ejemplo:
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
main() { int t,i,num[3][4];
for(t=0;t<3;t++) { for(i=0;i<4;i++) num[t][i] = (t*4)+i+1; } }
En este ejemplo, num[0][0] tendrá el valor 1, num[0][1] tendrá el valor 2, num[0][2] tendrá el valor 3, y así sucesivamente. El valor de num[2][3] será 12. El lenguaje C guarda los arrays bidimensionales en un array de filas y columnas, donde el primer índice indica la fila y el segundo la columna. Esta estructura significa que el índice más a la derecha cambia más rápido que el más a la izquierda cuando se accede a un array de elementos en el orden que C realmente almacena en memoria. Supongamos que definimos el siguiente array:
Buffer[5][8] , que se coloca en memoria a partir de la dirección “m”
565 6 PÁ GI N NA 2
POLITECNI CO
3.4 Array de Cadenas
En programación es corriente usar un array de cadenas, como por ejemplo para contener todas los textos que va a hacer aparecer el programa en un LCD. Para crear un array de cadenas, se usa un array de caracteres de dos dimensiones, en la que el tamaño del índice izquierdo determina el número de cadenas y el tamaño del derecho especifica la longitud máxima de todas las cadenas. Por ejemplo, esto declara un array de 30 cadenas con cada cadena como máximo de 80 caracteres (incluido el carácter nulo).
char Cad[30][80];
3.5 Array Multidimensionales
C permite arrays con más de dos dimensiones. El formato general de un declaración de array multidimensional es:
tipo nombre[tam1][tam2]….[tamN];
Por ejemplo, la siguiente sentencia crea un array multidimensional de 4x10x3
int tresd[4][10][3];
No se suelen usar array de tres o más dimensiones porque se incrementa geométricamente la cantidad de memoria
requerida. Por ejemplo un array de cuatro dimensiones de enteros con
dimensiones de 10x6x9x4 requeriría:
2 x 10 x 6 x 9 x 4 = 4.320 bytes
Si el array fuera double (8 bytes) la memoria requerida sería de 17.280 bytes
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
3.6 Inicializacion de Arrays
El formato general de la inicialización de un array es similar al de otras variables, según se muestra aquí:
especificador_de_tipo nombre_array[tam1]…[tamN] = { lista_de_valores};
La lista de valores es una lista separada por comas de constantes que son de tipo compatible con el tipo base del array. Por ejemplo:
int i[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
esta sentencia indica que i[0] tendrá el valor 1 e i[9] el 10.
Los array de caracteres que guardan cadenas permiten una inicialización abreviada que tiene la forma:
char nombre_array[tam]=”cadena”;
Por ejemplo:
char str[5] = “hola”;
este código es el mismo que este fragmento:
char str[5] = {‘h’, ‘o’, ‘l’, ‘a’, ‘\0’};
Recordar que cuando se inicializa una cadena, hay que dejar un espacio para el caracter nulo.
565 6 PÁ GI N NA 2
POLITECNI CO
Los arrays multidimensionales se inicializan de la misma manera que los de una dimensión, solo hay que tener en cuenta el orden en que el compilador asigna memoria al array, para saber a que índice van los valores asignados.
int Pot2[10][2] = { 1, 1, 2, 4, 3, 9, 4, 16, 5, 25, 6, 36, 7, 49, 8, 64, 9, 81, 10, 100};
3.7 Inicializacion de Arrays sin tamaño
En una sentencia de incialización de arrays, si no se especifica el tamaño de los mismos, el compilador C creará automáticamente un array lo suficientemente grande para guardar todos los inicializadores presentados.
char CadsT[]={“tamaño no definido de antemano”}
Después de esta sentencia el compilador le asignara a la cadena un tamaño de 31 bytes. Además de ser menos tedioso, el método de inicialización del array sin tamaño permite cambiar cualquier mensaje sin tener que preocuparse por errores accidentales de contaje. El lenguaje C no restringe las inicializaciones de arrays sin tamaños sólo a los arrays unidimensionales. Para arrays multidimensionales, se debe especificar todas las dimensiones excepto las de más a la izquierda para permitir indexar el array apropiadamente. En este sentido, se puede
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
contruir tablas de longitud variables mientras el compilador asigna automáticamente el almacenamiento suficiente. Por ejemplo, esta es la declaración de Pot2 de un array sin tamaño:
int Pot2[][2] = { 1, 1, 2, 4, 3, 9, 4, 16, 5, 25, 6, 36, 7, 49, 8, 64, 9, 81, 10, 100};
La ventaja de esta declaración sobre la de tamaño definido es que se puede alargar o acortar la tabla sin cambiar las dimensiones del array.
565 6 PÁ GI N NA 2
POLITECNI CO
FUNCIONES El lenguaje C está basado en el concepto de bloques construidos. Los bloques construidos se llaman funciones. Un programa C es una colección de una o más funciones. Para escribir un programa, primero se crean las funciones y después se ponen juntas. En C, una función es una subrutina que contiene una o más sentencias de C y eso realiza una o mas tareas
4.1 El formato general de una función
El formato general de un función C es:
especificador_de_tipo nombre_de_la_funcion ( declaración_de_parametros) { cuerpo de la función }
El especificador_de_tipo especifica el tipo del valor que la función devolverá mediante el uso de la sentencia return. El valor puede ser cualquier tipo válido. Si no se especifica un valor, entonces el compilador asume por defecto que la función devolverá un resultado entero. La lista de declaracion_de_parametros es una lista de nombres de tipos y nombres de variables separados por comas que recibe los valores de los argumentos cuando se llama a la función. No se tiene por qué incluir parámetros en una función, en ese caso la lista de parámetros estará vacía. Sin embargo, incluso si no se incluyen parámetros, se precisan los paréntesis. Se usará la sección que contiene las declaraciones de parámetros para definir el tipo de parámetros en la lista, si la función no tiene parámetros, no se necesita declaración de parámetros. El tipo de declaración es opcional (si no está una declaración explícita de tipo, el tipo de la función por defecto es entero). Las funciones terminan y devuelven automáticamente al procedimiento que las llamó cuando se encuentra la última llave. Se puede forzar la vuelta antes, usando la sentencia return. A diferencia de lo que puede ocurrir con la declaración de las variables, en la lista de parámetros de la función, cada parámetro tiene que tener su tipo y su nombre por separado de los otros.
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Esto quiere decir que la lista de declaración de parámetros de una función adquiere este formato general:
F(tipo varnombre1, tipo varnombre2…..tipo varnombreN)
Una declaración correcta sería:
F(int x, int y, float z)
Sin embargo, la siguiente declaración es incorrect:
F(int x, y, float z)
4.2 La sentencia return
La sentencia return tiene dos usos importantes. Primero , se puede usar para provocar la salida inmediata de la función en la que se está; es decir, return provocará que el programa en su ejecución vuelva al código que llamo a la función. Segundo, se puede usar return para devolver un valor. Cuando no está presente esta sentencia, simplemente se vuelve de la función al llegar al corchete de cierre de la misma. Cuando una función que no es void no contiene una sentencia return devuelve un cero.
4.3 Regla de ámbito de las funciones
Las reglas de ámbito de un lenguaje gobiernan si un trozo de código sabe o no, o accede o no, a otro trozo de código o datos. En C, cada función es un bloque discreto de código. Un código de función es exclusivo de la función y no es accesible desde otra función, por ejemplo, no se puede usar goto para saltar en medio de otra función.
565 6 PÁ GI N NA 2
POLITECNI CO
El código que comprende el cuerpo de la función está oculto del resto del programa y (a menos que el código use variables o datos globales), no puede afectar ni ser afectado por otras partes del programa. O sea, el código y los datos que se definen en una función no interactúan con el código o los datos que se definen en otra función, porque las dos funciones tienen un ámbito diferente. Hay tres tipos de variables: variables locales, parámetros formales y variables globales. Las reglas de ámbito gobiernan como pueden acceder otras partes del programa a estos tipos y establece el tiempo de vida de las variables.
4.3.1 Variables locales
Las variables locales pueden ser referenciadas por las sentencias que están dentro del bloque en el que dichas variables se declaran. Así las variables locales no son conocidas fuera de su propio bloque de código y su ámbito está limitado al bloque en el que se declaran. Uno de los aspectos más importantes de las variables locales es que existen sólo durante la ejecución de la función, y el almacenamiento de las mismas es en la pila, por lo tanto, no tienen asignado una posición de memoria fija durante toda la ejecución del programa, ya que esta asignación se hace en el momento de crearla, después de entrar a la función donde está definida, que dependerá del estado actual de la pila en ese momento. Por ejemplo:
Func1() { int x;
x = 10; }
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Func2() { int x;
x = -199; }
Aquí la variable entera x se declara dos veces, una vez en cada función. La x en la Func1() no tiene conexión con la x de la Func2(), ya que cada x se conoce sólo por el código que está en el mismo bloque que la declaración de la variable. Si le echaramos un vistazo al stack veríamos algo como lo siguiente.
Donde se aprecia el espacio que se le asigna en el stack a los parámetros de la función a ser llamada y a las variables locales de la misma.
565 6 PÁ GI N NA 2
POLITECNI CO
4.3.2 Parámetros formales
Si una función va a utilizar argumentos, deben declararse variables que acepten los valores de estos argumentos. Aparte de recibir los parámetros de entrada de la función, se comportan como otras variables locales dentro de la misma. Se tiene que tener cuidado, que los argumentos que se pasan son del mismo tipo que los parámetros de la función.
4.3.3 Variables Globales
Al contrario de las variables locales, las globales se conocen a través del programa entero y se pueden usar en cualquier trozo del código, ya que tienen asignado una posición definida y fija en la memoria Ram. La variables globales se crean declarándolas fuera de cualquier función, de esta forma es accesible a cualquier función dentro del archivo fuente donde fueron creadas. Por lo tanto para que estas variables globales sean “visibles” en los otros archivos fuentes que conforman el programa se usa el especificador extern que le dice al linqueador que esas variables ya han sido previamente declaradas. Por ejemplo:
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Si una variable global y una local tienen el mismo nombre, todas las referencias a ese nombre de variable dentro de la función en la que se declara como local se referirá a la local y no afectará a la variable global. Las variables globales son muy útiles cando se usa el mismo dato en muchas funciones del programa. Sin embargo, se debería evitar el uso innecesario de las variables globales, por las siguientes razones:
Ocupan memoria durante toda la ejecución del programa y no solo cuando se necesita
El uso de una variable global en lugar de una local restringirá la generalidad de una función ya que depende de una variable que debe definirse fuera de si misma.
Usando una serie grande de variables globales se pueden conducir a errores de programa con efectos laterales desconocidos e indeseables.
Una de las características principales de un lenguaje estructurado es la compartimentación del código y los datos. En C se construye la compartimentación usando variables locales y funciones. Por ejemplo, he aquí dos formas de escribir mul():
General
Especifico int x,y;
mul(int x, int y)
mul()
{
{ return(x*y);
}
return(x*y); }
Ambas funciones devuelven el producto de las variables x e y. Sin embargo, se puede usar la versión generalizada, o parametrizada, para devolver el producto de cualesquiera dos números, mientras que se puede usar la versión específica para encontrar solo el producto de las variables globales x e y.
565 6 PÁ GI N NA 2
POLITECNI CO
4.3.4 Punteros
Un puntero es una variable que contiene una dirección de memoria. Si una variable contiene la dirección de otra variable, entonces se dice que la primera variable apunta a la segunda. Como ilustra la siguiente figura:
El formato general para la declaración de una variable de puntero es:
tipo *nombre_variable;
donde tipo puede ser cualquier tipo base en C y nombre_variable es el nombre de la variable, además aparece el operador * que define que la variable va a contener como dato direcciones de memoria. Por ejemplo:
char *pch; int *pint;
En este ejemplo la variable pch es un puntero a una variable tipo char, es decir se le debe asignar la dirección de una variable de este tipo, la variable pint es un puntero a una variable tipo int, es decir se le debe asignar la dirección de una variable de este tipo.
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
4.3.5 Los operadores punteros
Un puntero tiene la capacidad de leer el contenido de la posición de memoria a la que apunta, por esta razón al trabajar con punteros necesitamos dos operadores básicos:
Operador &: Nos permita extraer la dirección de una variable para poder asignársela al puntero
Operador *: Nos permite extraer el contenido de la dirección a la que apunta el puntero
Ejemplo: main() { int cont, *punt_cont, val;
cont = 100; punt_cont = &cont; val = *punt_cont; }
Después de las definición de las variables, la primera línea le asigna el valor 100 a la variable cont, la segunda le asigna la dirección de cont a la variable de puntero punt_cont (no su contenido) y
la última sentencia asigna el contenido de la variable apuntada por punt_cont (o sea 100) a la variable val.
Aquí en esta última sentencia se ve la importancia de asignarle al puntero el tipo que corresponda ya que en esta toma 2 bytes a partir de la dirección que contiene y lo asigna a la variable val, porque es un puntero a un entero.
4.3.6 Aritmética de Punteros
En C se puede usar solo dos operaciones aritméticas sobre punteros: + y - . 565 6 PÁ GI N NA 2
POLITECNI CO
Cada vez que se incremente un puntero , apuntará a la posición de memoria del elemento siguiente en función de su tipo base. Veamos el siguiente ejemplo
int *p1, Varint;
p1 = &Varint; p1++;
(supongamos que la variable Varint esta en la dirección de memoria 2000) (Después del incremento p1 = 2002 y ¡no 2001!)
El lenguaje C no limita sólo a incremento y decrementos. También se pueden sumar o restar enteros a punteros. La expresión:
p1 = p1 + 5;
hará que p1 apunte al quinto elemento del tipo base de p1 después del que apunta actualmente, todo lo dicho para la suma vale también para la resta.
4.3.7 Punteros y Array
Existe un relación estrecha entre los punteros y los arrays. Veamos el siguiente ejemplo.
char str[20], *p1;
p1= str;
Este fragmento pone en p1 la dirección del primer elemento del array str. En C, un nombre de array sin un índice es la dirección de comienzo del array. El mismo resultado se puede generar interpretando el primer elemento del array como variable:
p1 = &str[0];
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Un punto importante más a tener en cuenta que si quisiéramos acceder al quinto elemento de este array podemos hacerlo de alguna de las 3 siguientes formas:
str[4] = *(p1+4) = p1[4]
O sea en lenguaje C la sentencia p[t] es idéntica a (p+t) .
Por último a pesar de la gran flexibilidad que aporta al lenguaje debe advertirse que los punteros entrañan un peligro potencial, ya que si se usa un puntero antes de inicializarlo con una dirección válida, las consecuencias para el programa pueden ser catastróficas. El programador es responsable del inicializar los punteros del programa antes de utilizarlos por primer vez.
4.4 Argumentos y parámetros de una función
Al pasar los argumentos hacia una función el programador debe asegurarse que son del tipo esperado por las misma, de lo contrario se podrían producir resultados inesperados. Por ejemplo, si una función espera un argumento char pero se la llama con un entero, entonces el compilador usará el primer byte del entero y lo asignara al argumento de la función. Existen dos formas en que se pueden pasar los argumentos a una función; el primero se denomina llamada por valor y la segunda llamada por referencia.
4.4.1 Llamada por valor
Este método copia el valor de un argumento en el parámetro formal de la función. Por tanto, los cambios que se hacen a los parámetros de la función no tienen efecto en las variables que se usan para llamarla.
565 6 PÁ GI N NA 2
POLITECNI CO
Por ejemplo: main() { int t = 10;
Sqr(t); } long Sqr(int x) { x = x*x; return(x); }
Este programa copia el valor del argument a Sqr(), que es 10, en el argumento x. Cuando tiene lugar la asignación x=x*x, la única cosa que se modifica es la variable local x. La variable t, usada para llamar a Sqr(), todavía tendrá el valor 10.
4.4.2 Llamada por referencia
Este método copia la dirección de un argumento en el parámetro. Dentro de la función, la dirección se utiliza para acceder al argumento real usado en la llamada. Esto significa que lo que se hace con el parámetro afectará la variable usada para llamar a la función. Aunque la convención de paso de parámetros de C es llamar por valor, se puede simular una llamada por referencia pasando un puntero al argumento. Veamos un ejemplo:
Intercambiador(int *y, int *x) { int temp;
temp = *x;
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
*x = *y; *y = temp; }
La función intercambiara el contenido entre dos variables enteras, de esta forma:
main() { int x = 10; int y = 20;
Intercambiador(&x, &y); }
Después de ejecutar este código se tendrá: x = 20, y = 10.
Cuando se usa un array como un argumento a la función, se pasa sólo la dirección del array y no una copia del array completo. O sea se hace una llamada por referencia.
4.5 Prototipos de Funciones
El lenguaje C interpreta por defecto que la funciones devolverán un valor entero (int), por lo tanto cuando la función devuelve un tipo de datos diferente o no devuelve datos (void), se le debe informar previamente al compilador antes de hacer la primera llamada a la misma. Esta definición que se hace de la función previó a cualquier código se la denomina: el prototipo de la función, donde también se definen el tipo y la cantidad de argumentos que la misma toma. Este proceso es la única manera para que el compilador C pueda generar el código correcto para estas funciones.
565 6 PÁ GI N NA 2
POLITECNI CO
Ejemplo:
void sqr(int *i);
main() { int x;
x = 10; sqr(&x); }
void sqr(int *i) { *i = *i * *i; }
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
565 6 PÁ GI N NA 2
POLITECNI CO
ESTRUCTURAS, UNIONES Y CAMPOS DE BITS El lenguaje C, permite la creación de tipos de datos personalizados. Entre los cuales analizaremos las estructuras, las uniones y las enumeraciones.
5.1 Estructuras
Una estructura es una colección de variables que se referencian bajo el mismo nombre. Una estructura proporciona un medio conveniente para mantener juntas información relacionada. Generalmente, todos los elementos en la estructura son relacionados lógicamente unos con otros. Por ejemplo, se puede representar la información de nombre y dirección de una lista de correo en una estructura. El formato general de una definición de estructura es:
struct nombre_estructura{ tipo nombre_var1; tipo nombre_var2; …………………..; tipo nombre varN; } variables_estructuras;
La palabra clave struct dice al compilador que se está definiendo una plantilla de estructura.
Struct Dat{ char nombre[30]; char calle[40]; char ciudad[20]; char provincia[3]; unsigned long código; };
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Así como está, realmente no ha sido declarada ninguna variable. El código sólo ha definido el formato de los datos. Para declarar un variable real con esta estructura, se escribiría:
struct Dat Info;
Esta línea declarará una variable de estructura de tipo Dat llamda Info, también se puede definir una o más variables en el momento en que la estructura es definida.
Struct Dat{ char nombre[30]; char calle[40]; char ciudad[20]; char provincia[3]; unsigned long código; }Info, marketing, social;
En este caso se declara tres variables de estructuras del tipo Dat. Si solo se necesita una variable estructurada, no se necesita incluir el nombre de la estructura:
Struct { char nombre[30]; char calle[40]; char ciudad[20]; char provincia[3]; unsigned long código; }Info;
565 6 PÁ GI N NA 2
POLITECNI CO
5.1.1 Referenciando elementos estructurados
Para referenciar una variable de la estructura se usa el siguiente formato:
nombre_estructura.nombre_variable
Así, por ejemplo si quisiéramos asignarle un valor a la variable código de la estructura Info, tendríamos que hacerlo así:
Info.codigo = 12345;
5.2 Arrays de estructuras
Para declarar un array de estructura, primero se debe definir una estructura y después declarar una variable de array de ese tipo. Por ejemplo, para declarar una array de cien elementos de la estructura Dat que a sido definida, se debería escribir:
struct Dat Info[100];
Así, para referenciar a la variable código de la tercera estructura del array, tendríamos que hacerlo de la siguiente forma:
Info[2].codigo = 12548;
5.2.1 Pasando Estructuras a funciones
La variables que conforman una estructuras no son diferentes a la demás variables no estructuradas, por lo tanto se pueden pasar a una función de las dos formas ya conocidas, por valor o por referencia. Supongamos tener la siguiente estructura:
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
struct T{ char x; int y; float z; char s[10]; }muestra;
Si pasamos los valores por valor tenemos:
Func(muestra.x);
// se pasa el valor del carácter x
Func1(muestra.z);
// se pasa el valor float de z
Func2(muestra.s[3]);
// pasa el valor del cuarto elemento de la cadena s
Si pasáramos los valores por referencia y con ello la función podría alterar el valor de la variable de la estructura, tenemos:
Func3(muestra.s);
// pasa la dirección de la cadena s
Func4(&muestra.y);
// pasa la dirección de la varible y
Func5(&muestra.s[2]);// pasa la dirección del tercer elemento de la cadena s
5.2.2 Punteros a Estructuras
Se declara un puntero de estructura poniendo el “ *” delante del nombre de la variable de estructura. Por ejemplo:
struct T *pmuestra;
El principal uso para los puntero de estructuras es la de poder pasarla hacia una función por referencia, es decir, pasar su dirección. 565Por ejemplo: 6 PÁ POLITECNI GI CO N NA 2
pmuestra = &muestra;
Se pueden referenciar los elemento de la estructura con su puntero de la siguiente forma:
(*pmuestra).z = 2.3654;
Los paréntesis son necesarios alrededor del puntero porque el operador de punto tiene una prioridad más alta que el operador *. Otra forma de escribir lo mismo es a través de un nuevo operador:
pmuentra->z = 2.3654;
5.2.3 Inicialización de Estructuras
Como cualquier otra variable la estructuras pueden ser inicializadas. Ejemplo:
struct T{ char x; int y; float z; char s[10]; };
struct T muestra = {25,5896,52,"Valor"};
5.3 Campos de Bits
Al desarrollar software para los microcontroladores se hace rutinario acceder a bits dentro de los registros que configuran su funcionamiento. Además muchas veces necesitamos, dentro del software, usar banderas para indicar alguna condición o estado del sistema y sería un desperdicio de memoria usar un byte completo para solo guardar 2 valores posibles (Verdadero o Falso). Otras veces
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
necesitamos guardar un rango de valores pequeños, que nos alcanzaría agrupando una pequeña cantidad de bits, por ejemplo, 4 bits para 16 opciones o condiciones. O si quisiéramos representar un valor menor a 512 nos alcanzaría con 9 bits. Se ve claro que ofrecería una ventaja, un lenguaje que pueda superar la discretización de 8 bits de las memorias, permitiéndonos formar variables de un ancho arbitrario de bits que pueda contener lo más justo posible la cantidad de valores a representar. El lenguaje C provee un método muy eficiente para poder acceder a los bits de una posición de memoria, o agrupar un número arbitrario de bits formando una variable. Este método se basa en las estructuras. Un campo de bits es justamente un tipo especial de estructura que define la longitud en bits que tendrá cada elemento. El formato general de una definición de campo de bit es:
struct nombre_estructura{ type nombre_1:longitud; type nombre_2:longitud; type nombre_3:longitud; type nombre_4:longitud; …………… }variable;
Por ejemplo:
struct S{ unsigned EEpWr:2;
// 0:Nada 1:Erase 2:Espera 3:Write
unsigned ActEquip:1; // 1:Encendido en proceso unsigned ApagEquip:1;
// 1: Apagado en proceso
unsigned ContManual:1;
// 1: La salida del los PWM se controla desde el display
unsigned OptLCD:3;
//
unsigned char ValorPWM; }Op; 565 6 PÁ GI N NA 2
POLITECNI CO
Como se ve en el ejemplo se pueden mezclar tipos estándar como char, int, etc. con campos de bits.
5.4 Uniones
En C, una unión es una posición de memoria que se usa por varias variables similares, que pueden ser de tipos diferentes. La definición de una unión es similar a la de una estructura, como se demuestra aquí:
unión u_tipo{ int i; char ch; };
Como con una declaración de estructura, esta definición no declara ninguna variable. Se puede declarar una variable poniendo su nombre al final de la definición o usando una sentencia separada de declaración. Para declarar una variable unión cnvt de tipo u_tipo se escribirá:
unión u_tipo cnvt;
En cnvt, tanto el entero i y como el carácter ch, comparten la misma posición de memoria, a pesar de tener distintas dimensiones. La siguiente figura muestra en la forma en que lo hacen:
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Para acceder a un elemento de unión se usa la misma sintaxis que se usara para las estructuras: el operador de punto y el operador de flecha .
Si se está accediendo el elemento de unión directamente, se usa el operador punto. Si se accede a la unión a través de un puntero, se usa el operador flecha. Por ejemplo, para asignar el entero 20 al elemento i de cnvt, se escribirá:
cnvt.i = 20;
En el ejemplo siguiente, el código pasa un puntero a la variable de unión del tipo u_tipo:
void func(unión u_tipo *un) { un->i = 20; }
En C se usan frecuentemente las uniones para extraer los bytes constituyentes de una variable no char. Supongamos que tenemos un entero y queremos extraer y byte bajo y el byte alto, para resolver
este problema, el C no ofrece muchas alternativas, pero usando una unión podríamos hacerlo de la siguiente forma:
unión pw{ int i; char bytes[2]; }Entero;
Si quisiéramos transmitir los bytes constituyentes de la variable entera ValNum se podría hacer de la siguiente forma:
Entero.i = ValNum; Rx(Entero.bytes[0]); 565Rx(Entero.bytes[1]); 6 PÁ POLITECNI GI CO N NA 2
// Se transmite el byte bajo de ValNum // Se transmite el byte alto de ValNum
Las uniones podrían contener estructuras y viceversa. Ejemplo:
struct S{ unsigned EEpWr:2;
// 0:Nada 1:Erase 2:Espera 3:Write
unsigned ActEquip:1; // 1:Encendido en proceso unsigned ApagEquip:1;
// 1: Apagado en proceso
unsigned ContManual:1;
// 1: La salida del los PWM se controla desde el display
unsigned OptLCD:3;
//
unsigned char ValorPWM; };
unión Con{ struct S Flag; int Banderas; } Proceso;
Supongamos que queremos poner todos los valores de la estructura a cero, una alternativa sería de la siguiente forma:
Proceso.Banderas = 0;
// Todos los bits de la estructura se irían a cero
Para poner a 1 el bit ActEquip lo haríamos:
Proceso. Flag.ActEquip = 1;
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
5.5 Enumeraciones Una enumeración es un conjunto de constantes enteras. A la enumeración se le asigna un nombre que, a todos los efectos, se comporta como un nuevo tipo de datos, de manera que las variables de ese tipo son variables enteras que solo pueden contener los valores especificados en la enumeración. La definición de una enumeración suele hacerse así: enum nombre_enumeración {constante1 = valor1, constante2 = valor2, ..., constanteN = valorN }; Por ejemplo: enum dias_semana {Lunes=1, Martes=2, Miercoles=3, Jueves=4, Viernes=5, Sabado=6, Domingo=7 }; Las variables que se declaren del tipo dias_semana serán, en realidad, variables enteras, pero sólo podrán recibir los valores del 1 al 7, así: dias_semana dia; dia = Lunes; dia = 1;
/* Las dos asignaciones son equivalentes */
Si no se especifican los valores en la enumeración, C les asigna automáticamente números enteros a partir de 0. Por ejemplo: enum dias_semana { Lunes, Martes , Miércoles, Jueves, Viernes, Sabado, Domingo};
en la anterior definición, la constante Lunes valdrá 0, Martes, 1, etc. Podemos alterar el orden en que se desarrolla la numeración de la siguiente forma:
enum dias_semana { Lunes, Martes , Miércoles, Jueves, Viernes=100, Sabado, Domingo};
Entonces tendremos:
Lunes= 0, Martes= 1, Miercoles= 2, Jueves= 3, Viernes= 100, Sabado= 101, Domingo= 102
565 6 PÁ GI N NA 2
POLITECNI CO
MPLAB C18 -VARIOS
6.1 Código START-UP
Todo código en C comienza en la dirección de memoria 0, que es el vector de reset de los microcontroladores PIC. Desde el vector de reset se salto a una función (start-up code) que inicializa los registros FSR1 y FSR2 para referenciar a la pila (software stack), opcionalmente se llama a una función para inicializar la sección idata ( donde están los datos que deben ser inicializados) desde la memoria de programa, y por ultimo llama en un loop a la función main(). Si el código de arranque (start-up code), inicializa o no la sección idata, va a ser determinado por cual de todas las opciones posibles de código de arranque fue usado. Los módulos c018i.o y c018i_e.o realizan la inicializaciones, mientras que los módulos c018.0 y c018_e.o no lo hacen. Según el ANSI C todas las variables definidas como static que no fueron inicializadas, deben ser explícitamente puestas a cero. Los códigos de start-up antes mencionados no lo hacen, para ello hay que usar los siguientes: c018iz.o o c018iz_e.0. Para realizar la inicialización de la memoria de datos, el enlazador MPLINK crea una copia de los datos a inicializar en la memoria de programa que el código de arranque se encarga de copiar a la memoria de datos. La elección del cual código de arranque que se usa se decide alterando el archivo .lkr del microcontrolador que estemos usando en el proyecto. El siguiente texto es el comienzo del contenido del archivo 18F242.lkr:
// File: 18f242.lkr // Sample linker script for the PIC18F242 processor LIBPATH .
FILES c018i.o FILES clib.lib FILES p18f242.lib
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
6.2 Mezclando C y Assembler
Es posible que código en C trabaje en forma coordinada con código escrito en lenguaje Assembler. Es decir, se puede llamar a rutinas de C desde Assembler y rutinas de Assembler desde el C, para que esto funcione adecuadamente, también tiene que poderse compartir variables entre estos códigos escritos en diferentes lenguajes. A continuación se verá cada caso particular.
6.2.1 Llamando a una function C desde Assembler
Cuando se llama a una función C desde assembler:
La función C debe ser declarada como un símbolo extern en el archivo de assembler
Para llamar a la función se debe usar la sentencia de assembler call o rcall.
Ejemplo:
Dado el siguiente prototipo para la función C:
char add(auto char x, auto char y);
El parámetro auto pone los datos en la pila (stack) desde la derecha a la izquierda. Para datos multi-bytes el byte más bajo es puesto primero en la pila. Para llamar a la función add con los valores x = 0x61 e y = 0x65, el valor de y debe ser puesto primero en la pila, seguido del valor de x. El valor retornado, como es de 8 bits, será puesto en el registro WREG. La forma de llamar a esta función desde un fuente de assembler será:
EXTERN add
; función definida en algún modulo C
... MOVLW 0x65 MOVWF POSTINC1 ; y = 0x65 se carga este valor en la pila 565MOVLW 0x61 6 PÁ POLITECNI GI CO N NA 2
MOVWF POSTINC1 ; x = 0x61 se carga este valor en la pila CALL add MOVWF result ; el resultado de la funcion es retornado en WREG ...
Ejemplo:
Dado el siguiente prototipo de función C:
int sub(auto int x, auto int y);
Para llamar a esta función con los valores x = 0x7861 e y = 0x1265, el valor de y debe ser puesto primero, seguido por el valor de x. El valor retornado, puesto que es de 16 bits, será retornado en los registros PRODH:PRODL.
EXTERN sub
; definida en un modulo C
... MOVLW 0x65 MOVWF POSTINC1 MOVLW 0x12 MOVWF POSTINC1 ; y = 0x1265 se la coloca en la pila MOVLW 0x61 MOVWF POSTINC1 MOVLW 0x78 MOVWF POSTINC1 ; x = 0x7861 se la coloca en la pila CALL sub MOVFF PRODL, result MOVFF PRODH, result+1 ; el resultado es devuelto en PRODH:PRODL ...
6.2.2 Llamando a una función Assembler desde C
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
Cuando se llama a una función de assembler desde el C:
El nombre de la función debe ser declarada global en el modulo ASM
La función de assembler debe ser declarada como extern en el modulo C
La función es llamada desde C usando notación estándar de C para llamar una función
Ejemplo, suponiendo la siguiente función en assembler:
UDATA_ACS delay_temp
RES 1 CODE
asm_delay GLOBAL asm_delay
; exporta el nombre de tal forma que
SETF delay_temp not_done DECF delay_temp BNZ not_done done RETURN END
Para llamar la función asm_delay desde el archivo de C, se debe agregar un prototipo externo para la función de assembler y llamar la función usando el formato estándar de C.
extern void asm_delay (void);
void main (void) { asm_delay (); } 565 6 PÁ GI N NA 2
POLITECNI CO
Otro ejemplo:
Dada la siguiente función de assembler:
INCLUDE "p18c452.inc" CODE asm_timed_delay GLOBAL asm_timed_delay ; se hace visible para el linkeador not_done ; el dato del tiempo es pasado en la pila y debe extraerse de la misma …… ; restando 1 al valor del puntero de pila FSR1 MOVLW 0xff
; se carga el valor 1
DECF PLUSW1, 0x1, 0x0
; se extrae el valor de la pila y se la almacena en WREG
BNZ not_done done RETURN END
Al igual que antes para llamar a la función desde un fuente de C, se debe agregar un prototipo para la función de assembler y la función debe ser llamada en forma estándar desde el código C.
extern void asm_timed_delay (unsigned char); void main (void) { asm_timed_delay (0x80); }
6.2.3 Usando variables C en Assembler
Cuando usamos variables de C en Assembler:
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
La variable de C debe ser global en el código C
La variable de C debe ser declarada como un símbolo extern en el archivo de Assembler
Dado el siguiente código en C:
unsigned int c_variable;
void main(void) { ……………. }
Para poder modificar la variable c_variable desde el assembler, en el archivo fuente de Assembler se le debe agregar una declaración extern para esta variable.
EXTERN c_variable ; variable definida en el modulo de C MYCODE
CODE
asm_function GLOBAL asm_function
MOVLW 0xff
; se va a cargar el valor 0xffff en la variable
MOVWF c_variable MOVWF c_variable+1 done RETURN END
6.2.3 Usando variables de Assembler en C
Cuando usamos variables de Asseembler en C: 565 6 PÁ GI N NA 2
La variable debe ser declarada como global en el modulo de ASM
POLITECNI CO
La variable debe ser declarada como extern en el modulo de C
Dado el siguiente código escrito en Assembler:
MYDATA
UDATA
asm_variable
RES
2;se guardan dos posiciones de memoria para la variable
GLOBAL
asm_variable
END
Para poder usar la variable asm_variable desde el fuente de C, se debe agregar una definición de extern para la variable en el código C. A partir de ahí se puede usar como cualquier otra variable de C.
extern unsigned in asm_variable;
void Funcion(void) { asm_variable = 0x1234; }
6.3 Inline Assembler
El MPLAB C18 permite provee una assembler interno que usa una sintaxis similar a la del MPASM. Este assembler permite colocar instrucciones de assembler, en medio del código C. El bloque de código assembler, debe comenzar con la directiva _asm y terminar con la _endasm. Este assembler tiene algunas limitaciones respecto del MPASM. Ejemplo: _asm MOVLW 10 MOVWF count, 0
// Mueve el decimal 10 a la variables count
POLITECNIC O
57
Lenguaje C para Microcontroladores PIC
start: DECFSZ count, 1, 0 GOTO done BRA start done: _endasm
// Realiza un loop hasta que count es cero
Es recomendable limitar el uso del assembler inline a un mínimo. Cualquier función conteniendo un porción de assembler inline no será optimizada por el compilador. Si se necesita escribir fragmentos largos de assembler conviene usar el MPASM y enlazarlos con los módulos de C a través del linqueador.
6.4 Interrupciones en C18
Los PIC 18Fxxxx poseen la posibilidad de elegir 2 niveles de prioridad para las interrupciones: la interrupción de baja prioridad cuyo vector de interrupción está en la dirección de la Flash 0x18 y la interrupción de alta prioridad cuyo vector de interrupción está en la dirección de la Flash 0x08. La interrupción de alta prioridad puede interrumpir a la de baja prioridad, pero no al revés. Cualquier fuente de interrupción del microcontrolador (Timer, conversor AD, PWM, etc) puede ser asignado indistintamente a una de las dos prioridades. Sabemos que al producirse una interrupción, si que esta estaba habilitada, el microcontrolador carga el contador de programa con la dirección de memoria 0x08 para la de alta prioridad o la 0x18 para la de baja prioridad. La pregunta que tenemos que hacernos es ¿Cómo lograremos manejar las interrupciones desde el lenguaje C?. Para lograr esto, no valdremos de una directiva de compilación del C llamada #pragma esta nos permite ubicar código en puntos específico de la memoria. En el MPLAB C18 disponemos de las siguientes directivas específicas:
#pragma interruptlow fname #pragma interrup fname
565 6 PÁ GI N NA 2
POLITECNI CO
El pragma interrupt declara a una función ser una rutina de servicio de interrupción de alta prioridad. El pragma interruplow declara a una función ser una rutina de servicio de interrupción de baja prioridad. Una interrupción suspende la ejecución de la aplicación actual, guarda el su contexto, y transfiere el control a su rutina de servicio de interrupción (RSI). Una vez completada la RSI, la información del contexto previo es restaurada y se reasume la ejecución normal de la aplicación. El contexto mínimo guardado y restaurado por una interrupción son los registros: WREG, BSR and STATUS. Una interrupción de alta prioridad usa los registros sombras para guardar y restablecer este contexto mínimo, mientras que una interrupción de baja prioridad usa la pila del programa. Como consecuencia de esto la interrupción de alta prioridad puede entrar y salir de las interrupciones mucho más rápido que la de baja prioridad.
6.4.1 Rutina de Servicio de Interrupción (RSI)
En MPLAB C18 una RSI, es como cualquier otra función de C, que puede tener variables locales y acceder a variables globales, sin embargo, esta debe ser declarada sin parámetros y no puede retornar un valor ya que una RSI es invocada asincrónicamente en respuesta a una interrupción de hardware. Las variables globales que son usadas por el programa principal y por la RSI deben ser declaradas volatile.
Las RSI deben ser invocadas solo por los eventos de hardware, y no por otra función C del programa. Debido a que la RSI usa la instrucción RETFIE para salir de la interrupción en vez de la interrupción RETURN usada por el resto de las funciones.
6.4.2 Vector de Interrupción
El MPLAB C18 no coloca automáticamente, en el vector de interrupción correspondiente (0x08 o 0x18) un salto hacia muestra rutina de interrupción, sino que, es el programador que debe colocar una instrucción goto hacia las rutinas RSI. Ejemplo:
POLITECNIC O
57