78888821-curso-ccs-en-pdf.pdf

69
CCS - Comentarios Los comentarios son útiles para informar al que lee nuestro código (o a nosotros mismos)el significado o funcionamiento de cada parte del programa. Todos los comentarios son ignorados por el compilador, por lo que no debes preocuparte por llenar la memoria del PIC . Un comentario puede ser colocado en cualquier parte del programa, excepto en medio de una palabra reservada, del nombre de una función o del nombre de una variable. Los comentarios pueden ocupar más de una línea de largo. Pueden utilizarse para deshabilitar momentáneamente un trozo de código. Hay dos formas de introducir un comentario. La primera es la misma que en cualquier otro compilador de C: /* Esto es un comentario */ Es decir, todo lo que haya escrito entre /* y */ será tomado por el compilador como un comentario. La segunda manera es la siguiente: // Esto es un comentario En este caso, el comentario comienza en // y se extiende hasta el final de la línea. CCS - Variables La programación seria prácticamente imposible sin el uso de variables. Podemos hacernos una imagen mental de las variables consistente en una caja en la que podemos guardar algo. Esa caja es una de las muchas que disponemos, y tiene en su frente pegada una etiqueta con su nombre. Estas cajas tienen ciertas particularidades, que hace que solo se puedan guardar en ellas determinados tipos de objetos.

Upload: guillermo-rgz-b

Post on 10-Aug-2015

58 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: 78888821-Curso-Ccs-en-PDF.pdf

CCS - Comentarios

Los comentarios son útiles para informar al que lee nuestro código (o a

nosotros mismos)el significado o funcionamiento de cada parte del programa.

Todos los comentarios son ignorados por el compilador, por lo que no debes

preocuparte por llenar la memoria del PIC.

Un comentario puede ser colocado en cualquier parte del programa, excepto en

medio de una palabra reservada, del nombre de una función o del nombre de

una variable.

Los comentarios pueden ocupar más de una línea de largo. Pueden utilizarse

para deshabilitar momentáneamente un trozo de código.

Hay dos formas de introducir un comentario. La primera es la misma que en

cualquier otro compilador de C:

/* Esto es un comentario */

Es decir, todo lo que haya escrito entre /* y */ será tomado por el compilador

como un comentario.

La segunda manera es la siguiente:

// Esto es un comentario

En este caso, el comentario comienza en // y se extiende hasta el final de la

línea.

CCS - Variables

La programación seria prácticamente imposible sin el uso de variables.

Podemos hacernos una imagen mental de las variables consistente en una caja

en la que podemos guardar algo. Esa caja es una de las muchas que

disponemos, y tiene en su frente pegada una etiqueta con su nombre. Estas

cajas tienen ciertas particularidades, que hace que solo se puedan guardar en

ellas determinados tipos de objetos.

Page 2: 78888821-Curso-Ccs-en-PDF.pdf

En esta analogía, cada caja es una variable, su contenido es el valor que

adopta, y la etiqueta es el nombre de la variable. Como su nombre lo indica, y

como veremos mas adelante, el contenido de una variable puede ser

modificado a lo largo del programa.

Tipos

El lenguaje C proporciona cinco tipos básico de datos, con cuatro modificadores posibles. Podemos utilizar variables de cualquiera de esos tipos. La tabla siguiente muestra los tipos disponibles:

Tipo Ancho (Bits) Rango

short 1 0 o 1

short int 1 0 o 1

int 8 0 a 255

char 8 0 a 255

unsigned 8 0 a 255

unsigned int 8 0 a 255

signed 8 -128 a 127

signed int 8 -128 a 127

long 16 0 a 65536

long int 16 0 a 65536

signed long 16 -32768 a 32767

float 32 3.4E-38 a 3.4E+38

Page 3: 78888821-Curso-Ccs-en-PDF.pdf

Si miras con atención la tabla anterior, puedes ver que hay tipos que parecen

estar repetidos. En realidad, ocurre que CCS permite una "forma corta" para

escribir algunos de los tipos. Concretamente, podemos utilizar unsigned, short,

o long en lugar de unsigned int, short int, o long int.

Declaración

Las variables deben ser declaradas antes de ser utilizadas en el programa. El

proceso de declaración de variables le dice a CCS de que tipo son y como se

llaman. Al igual las demás instrucciones CCS que veremos a lo largo de este

tutorial, debe terminar con ;.

La forma en que se declara una variable es la siguiente:

tipo nombre_de_la_variable;

Donde tipo es alguno de los enumerados en la tabla anterior. Veamos un

ejemplo:

int temperatura;

Esa línea permite a nuestro programa emplear la variable temperatura, que

será capaz de albergar cualquier valor comprendido entre 0 y 255.

Asignación de valores

Asignar un valor a una variable es una tarea bien simple. Basta con hacer lo

siguiente:

nombre_de_variable = valor;

donde nombre_de_variable es el nombre de la variable que contendrá el

valor. Al igual que todas las instrucciones de CCS, debe terminar con un ;

(punto y coma).

Page 4: 78888821-Curso-Ccs-en-PDF.pdf

Por ejemplo, supongamos que queremos asignar el valor "100" a la variable

"count". Lo hacemos de la siguiente manera:

count = 100;

donde 100 es una constante.

Podemos asignar un valor a una variable en el momento en que la declaramos.

lo siguientes son algunos ejemplos de esto:

int a = 0;

Hace que la variable a sea del tipo entero, y le asigna el valor 0.

signed long a = 125, b, c = -10;

a,b y c son declaradas como long. a toma el valor de "125" y c "-10".

Si la variable es de tipo char, la constante que se le asigna debe estar entre

tildes, como en el siguiente ejemplo:

char nombre = 'juan perez';

Por ultimo, tambien podemo asignar a una variable el contenido de otra. En el

siguiente ejemplo, el valor de i sera igual a 10.

int i = 10;

int j;

j = 1;

Variables Locales y Globales

Si una variable se declara dentro de una función, será "visible" solo dentro de

ésta:

Page 5: 78888821-Curso-Ccs-en-PDF.pdf

funcion1 () {

char letra;

. . . . }

En el ejemplo anterior, la variable tipo char llamada letra solo podrá utilizarse

dentro de la función funcion1 (). Si intentamos utilizarla fuera de ella, el

compilador nos dará un error.

Si declaramos una variable fuera de cualquier función, el alcance de esta sera

global, lo que quiere decir que estará disponible en cualquier parte de nuestro

programa. Vemos un ejemplo de este último caso.

char letra;

main() {

. . . .}

funcion1 () {

. . .}

La variable tipo char llamada letra podrá utilizarse dentro de main() o de

funcion1().

Conversiones entre tipos

CCS nos permite mezclar diferentes tipos de variables dentro de una misma

expresión. Y existen un conjunto de reglas que nos permiten saber que de que

tipo será el resultado de la misma.

Por ejemplo, el compilador convertirá automáticamente a int cualquier

expresión que contenga variables char, short o int. Esta conversión solo tiene

efecto mientras se realizan los cálculos. Las variables en sí mismas no

cambian su tipo.

Page 6: 78888821-Curso-Ccs-en-PDF.pdf

Las reglas de conversión de tipos hacen que el resultado de una operación sea

siempre el mismo que el de la variable más larga que intervenga en ella.

Sin embargo, podemos forzar a que el resultado sea de un tipo en particular, de

la siguiente forma:

(tipo) valor

donde tipo es el tipo al que queremos que pertenezca valor. El siguiente

ejemplo nos aclarará todo esto:

int a = 250, b = 10;

long c;

c = a * b;

Tal como explicamos, c no contendrá el valor 2500 como podría parecer a

simple vista, por que el tipo de c no se modifica. CCS calcula a * b' y

obtiene efectivamente el resultado 2500, pero c sólo contendrá los 8 bits

menos significativos de ese resultado, es decir, el decimal 196.

Si hubiésemos hecho: int a = 250, b = 10;

long c;

c = (long) (a * b);

el valor almacenado en c hubiese sido efectivamente 2500.

CCS - Directivas para el compilador

Llamadas en inglés "preprocessor directives", son comandos que interpreta el

primer paso de la compilación que lleva a cabo CCS.

Las directivas más comunes son #define e #include, pero deberías dar un

vistazo a todas.

#ASM / #ENDASM

Page 7: 78888821-Curso-Ccs-en-PDF.pdf

Este par de instrucciones permite que utilicemos un bloque de instrucciones en

assembler dentro de nuestro código CCS. El siguiente es un ejemplo de uso

tomado de la ayuda del CCS:

int find_parity (int data) {

int count;

#ASM

movlw 0x8

movwf count

movlw 0

loop:

xorwf data,w

rrf data,f

decfsz count,f

goto loop

movlw 1

awdwf count,f

movwf _return_

#ENDASM

}

La variable predefinida _RETURN_ puede utilizarse para transferir un valor

desde el código ASM a CCS.

Si en lugar de #ASM utilizamos #ASM ASIS, CCS no intentará efectuar

cambios de bancos de memoria automáticos para las variables que no pueden

ser accedidas desde el banco actual. El código assembler es utilizado "as-is"

("como es").

#BIT

Permite crear una nueva variable de un bit de tamaño, que es colocada en la

memoria del PIC en la posición del byte x y el bit y. Esto es muy útil para

acceder de una manera sencilla a los registros. Por supuesto, estas variables

Page 8: 78888821-Curso-Ccs-en-PDF.pdf

pueden ser empleadas de la misma manera que cualquier otra variable tipo

short. El formato de #BIT es el siguiente:

#BIT nombre = x.y

Donde nombre es un nombre de variable CCS válido, x es una constante o

una variable CCS válida e y es una constante de 0 a 7.

Estos son algunos ejemplos de uso:

#BIT T0IF = 0xb.2

.

.

.T0IF = 0; // Limpia el flag de interrupción del Timer 0

int resultado;

#BIT resultado_primer_bit = resultado.0

.

.

.if (resultado_primer_bit)

#BYTE

Permite crear una nueva variable de un Byte de tamaño, que es colocada en la

memoria del PIC en la posición del byte x. Esta es una herramienta muy útil

para acceder de una manera sencilla a los registros. Por supuesto, estas

variables pueden ser empleadas de la misma manera que cualquier otra

variable tipo int. El formato de #BYTE es el siguiente:

#BYTE nombre = x

Donde nombre es un nombre de variable CCS válido, y x es una constante o

una variable CCS válida.

Estos son algunos ejemplos de uso:

#BYTE STATUS = 3

#BYTE PORTB = 6

Page 9: 78888821-Curso-Ccs-en-PDF.pdf

#DEFINE

La instrucción #define tiene la siguiente forma:

#DEFINE <label> value

<label> es la etiqueta que usaremos en nuestro programa. Y value es el valor

que estamos asignando a esta etiqueta. Las instrucciones #DEFINE no

generan código ASM, si no que el preprocesador realiza los reemplazos que

ellas indican en el momento de la compilación. El uso de #DEFINE permite

construir programas más ordenados y fáciles de mantener.

Veamos algunos ejemplos de #DEFINE

#DEFINE TRUE 1

Cada vez que en nuestro programa aparezca la etiqueta TRUE, el

precompilador la reemplazará por 1

#DEFINE pi 3.14159265359

Cada vez que en nuestro programa aparezca la etiqueta pi, el precompilador la

reemplazará por 3.14159265359

#DEFINE MENOR_DE_EDAD (EDAD < 18)

.

.

.

.

.

if MENOR_DE_EDAD

printf(“JOVEN”);

El ejemplo anterior permite una mayor claridad en el programa. Por supuesto,

no hay que abusar de #DEFINE, por que podemos obtener el efecto contrario,

haciendo nuestros programas bastante difíciles de comprender.

Page 10: 78888821-Curso-Ccs-en-PDF.pdf

#DEFINE es una potente herramienta para la creación de macroinstrucciones,

ya que soporta el uso de variables. Veamos algunos ejemplos de esto:

#DEFINE var(x,v) unsigned int x=v;

var(a,1)

var(b,2)

var(c,3)

Cuando el preprocesador se encuentra con el código anterior, hace lo mismo

que si hubiésemos escrito lo siguiente:

unsigned int a=1;

unsigned int b=2;

unsigned int c=3;

Como puedes ver, #DEFINE puede hacer mucho por tus programas.

#DEVICE

Esta directiva informa al compilador que arquitectura de hardware utilizaremos,

para que pueda generar código apropiado para la cantidad de RAM, ROM y

juego de instrucciones disponibles. Para los chips con más de 256 bytes de

RAM se puede seleccionar entre emplear punteros de 8 o 16 bits. Si deseamos

emplear punteros de 16 bits basta con añadir *=16 a continuación del nombre

microcontrolador seleccionado.

Veamos algunos ejemplos:

#DEVICE PIC16C74 //PIC 16C74, punteros de 8 bits.

#DEVICE PIC16C67 *=16 //PIC 16C67, punteros de 16 bits.

Hay más opciones que podemos agregar en las líneas #DEVICE:

ADC=x : Determina el número de [bit]]s que devuelve la función

read_adc().

Page 11: 78888821-Curso-Ccs-en-PDF.pdf

#DEVICE PIC16F877 *=16 ADC=10 //PIC 1616F877, punteros de 16 bits y 10

bits en el ADC.

ICD=TRUE : Genera código compatible con el ICD de

[www.microchip.com Microchips]].

#DEVICE PIC16F877 ICD=TRUE//PIC 1616F877, punteros de 8 bits y código

para ICD.

WRITE_EEPROM=ASYNC :

HIGH_INTS=TRUE : Define la prioridad de las interrupciones en los

PIC18.

#FUSE

Permite modificar el valor de los fuses del microcontrolador que estamos

empleando. Los valores posibles dependen de cada microcontrolador en

particular, y los valores posibles se cargan al utilizar #INCLUDE seguido del

archivo correspondiente. La forma de #FUSE es la siguiente:

#FUSE opciones

Donde opciones es una lista de las opciones posibles separadas mediante

comas. Antes de seguir, recuerda que puedes ver dentro del archivo con

extensión .h correspondiente cuales son los valores posibles para ese

microcontrolador. Están al comienzo del archivo, en forma de comentarios.

Algunos valores comunes son

Tipo de oscilador: LP, XT, HS, RC

Wach Dog Timer: WDT, NOWDT

Protección de código: PROTECT, NOPROTECT

Power Up Timer: PUT, NOPUT

Brown Out Reset: BROWNOUT, NOBROWNOUT

#INCLUDE

Page 12: 78888821-Curso-Ccs-en-PDF.pdf

Permite incluir en nuestro programa uno o mas archivos (conocidos como

header file) que posean extensión .h. Estos archivos contienen información

sobre funciones, sus argumentos, el nombre de los pines de un modelo

determinado de PIC o cualquier otra cosa que usemos habitualmente en

nuestros programas. Esto permite no tener que escribir un montón de cosas

cada vez que comenzamos un programa nuevo: basta con incluir el .h

correspondiente.

La forma de utilizar esta instrucción es la siguiente:

#INCLUDE <archivo>

Esto hará que el contenido de <archivo> se compile junto con nuestro

programa. Por ejemplo:

#INCLUDE <PIC16F877A.H>

hace que todas las especificaciones de nombres y registros del PIC16F877A se

incluyan en nuestro programa. Esto permitirá referirnos al pin 0 del PORTB del

PIC mediante PIN_B0.

Existe la posibilidad de utilizar #INCLUDE "archivo" en lugar de #INCLUDE

<archivo>. La diferencia es que si usamos "", el archivo se buscará primero

en el directorio actual. Si empleamos <>, el archivo será buscado primero en la

ruta por defecto para los archivos .h.

#INT_xxx

#INT_xxx indica que la función que le sigue (en el código fuente CCS) es una

función de interrupción. Estas funciones no deben tener parámetros. Por

supuesto, no todos los PICs soportan todas las directivas disponibles:

1. INT_AD Conversión A/D finalizada.

2. I NT_ADOF Conversión A/D timeout.

3. INT_BUSCOL Colisión en bus.

4. INT_BUTTON Pushbutton.

5. INT_CCP1 Unidad CCP1.

Page 13: 78888821-Curso-Ccs-en-PDF.pdf

6. INT_CCP2 Unidad CCP2.

7. INT_COMP Comparador.

8. INT_EEPROM Escritura finalizada.

9. INT_EXT Interrupción externa.

10. INT_EXT1 Interrupción externa #1.

11. INT_EXT2 Interrupción externa #2.

12. INT_I2C Interrupción por I2C.

13. INT_LCD Actividad en el LCD.

14. INT_LOWVOLT Bajo voltaje detectado.

15. INT_PSP Ingreso de datos en el Parallel Slave Port.

16. INT_RB Cambios en el port B (B4-B7).

17. INT_RC Cambios en el port C (C4-C7).

18. INT_RDA Datos disponibles en RS-232.

19. INT_RTCC Desbordamiento del Timer 0 (RTCC).

20. INT_SSP Actividad en SPI o I2C.

21. INT_TBE Buffer de transmisión RS-232 vacío.

22. INT_TIMER0 Desbordamiento del Timer 0 (RTCC).

23. INT_TIMER1 Desbordamiento del Timer 1.

24. INT_TIMER2 Desbordamiento del Timer 2.

25. INT_TIMER3 Desbordamiento del Timer 3.

Ejemplo:

#int_ad

adc_handler () {

adc_active=FALSE;

}

#int_rtcc noclear //"noclear" evita que se borre el flag correspondiente.

isr () {

...

}

Page 14: 78888821-Curso-Ccs-en-PDF.pdf

CCS - Operadores

En CCS los operadores cumplen un rol importante. Quizás C sea uno de los

lenguajes que más operadores tiene. Una expresión es una combinación de

operadores y operandos. En la mayoría de los casos, los operadores de CCS

siguen las mismas reglas que en álgebra, y se llaman de la misma manera.

Operadores aritméticos

CCS posee cinco operadores aritméticos:

+ (suma) - (substracción) * (multiplicación) / (división) % (módulo)

Los primeros cuatro operadores mencionados se pueden utilizar con cualquier tipo de dato. Estos son algunos ejemplos de como usarlos:

a = b + c;a = b - c;a = b * c;a = b / c;a = -a; //Cambia el signo de "a".a = a + 1; //suma 1 al valor de "a".

El operador % (módulo) solo puede emplearse con enteros. Devuelve el resto de una división de enteros. Veamos un par de ejemplos:

int a = 10, b = 5, c;c = a % b; //"c" valdrá cero.int a = 20, b = 3, c;c = a % b; //"c" valdrá 2.

Atajos

CCS también provee atajos para utilizar los operadores aritméticos. Hay algunas operaciones que se repiten a menudo cuando creamos nuestros programas, y estos atajos ayudan a que podamos escribir nuestro código más rápidamente. Los atajos provistos son los siguientes.

a *= b es lo mismo que a = a * b a /= b es lo mismo que a = a / b a += b es lo mismo que a = a + b

Page 15: 78888821-Curso-Ccs-en-PDF.pdf

a -= b es lo mismo que a = a - b a %= b es lo mismo que a = a * b

Operadores Relacionales

Los operadores relacionales comparan dos valores, y devuelven un valor lógico basado en el resultado de la comparación. Los operadores relacionales disponibles son los siguientes:

> mayor que >= mayor que o igual a < menor que <= menor que o igual a == igual a != distinto de

el resultado de la comparación, sera siempre 0 o 1. 0 significa que el resultado de la comparación ha sido falso, y 1 que ha sido verdadero.

Operadores Lógicos

Los operadores lógicos disponibles permiten realizar las operaciones AND, ORy NOT:

p && q significa p AND q p || q significa P OR q !p significa NOT p

Por supuesto, puede emplearse más de un operador lógico en la misma expresión:

a = b && ( q || n )

Y se pueden comninar con los demas operadores vistos:

a = !(maximo <= 100) //a sera 1 si maximo es mayor que 100.

Operadores de bits

Existen seis operadores pensados para trabajar directamente sobre los bits. Solamente pueden usarse con variables tipo int y char. Son los siguientes:

& (AND) | (OR) ^ (XOR) ~ (complemento)

Page 16: 78888821-Curso-Ccs-en-PDF.pdf

<< (desplazamiento a la izquierda) >> (desplazamiento a la derecha)

Estas operaciones se llevan a cabo bit por bit. Veamos un ejemplo:

Supongamos que a = 120 y b = 13.

a & b = 8 a | b = 125 a ^ b = 117 ~ a = 135

El porqué de estos resultados puede comprenderse mejor si se pasan los valores de a y b a binario:

a = 11111000b = 00001101

luego

01111000 AND 00001101 = 0000100001111000 OR 00001101 = 0111110101111000 XOR 00001101 = 01110101NOT 01111000 = 10000111

Los operadores de desplazamiento "corren" el contenido de la variable a la derecha o a la izquierda, rellenando con ceros. Veamos algunos ejemplos:

a = a >> 2 //"corre" el contenido de a dos lugares a la derecha

Si a era igual a 120 ( 01111000 en binario) pasará a valer 30 (00011110 en binario).

a = a << 3 //"corre" el contenido de a cinco lugares a la izquierda

Si a era igual a 120 (01111000 en binario) pasará a valer 192 (11000000 en binario).

Atajos

CCS también provee atajos para utilizar los operadores de bits. Hay algunas operaciones que se repiten a menudo cuando creamos nuestros programas, y estos atajos ayudan a que podamos escribir nuestro código más rápidamente. Los atajos provistos son los siguientes.

a <<= b es lo mismo que a = a << b a >>= b es lo mismo que a = a >> b a &= b es lo mismo que a = a & b a |= b es lo mismo que a = a | b

Page 17: 78888821-Curso-Ccs-en-PDF.pdf

a ^= b es lo mismo que a = a ^ b

Otros operadores

Quedan por ver aun dos operadores más:

++ Operador incremento -- Operador decremento

Estos operadores permiten sumar (o restar) uno al valor de una variable. Lo que generalmente haríamos así:

a = a + 1

0 así:

a = a - 1

lo podemos hacer así:

a++

o así:

a--

el resultado sera el mismo, pero es mas corto de escribir, y mas fácil de utilizar en expresiones complejas.

Precedencia de los operadores

Al igual que ocurre en álgebra, en CCS los operadores se evalúan en un orden determinado. La siguiente lista muestra este orden, ordenado de mayor a menor:

() signo +, signo -, ++, --, !, (<tipo>) *, /, % +, - <, <=, >, >= ==, != &&, || =, +=, -=, *=, /=, %=

Page 18: 78888821-Curso-Ccs-en-PDF.pdf

CCS - Punteros

Una de las características más interesantes de las diferentes versiones de C

son los punteros. Por supuesto, CCS permite el manejo de punteros, con lo

que nuestros programas pueden aprovechar toda la potencia de esta

herramienta.

El presente artículo fue escrito por Pedro (PalitroqueZ), un amigo de uControl.

Su dirección de correo electrónico es [email protected].

¿Qué es un puntero?

Un puntero es una variable cuya finalidad es almacenar números ENTEROS

POSITIVOS. Estos números no son números al azar, son direcciones de la

memoria que posee el hardware del microcontrolador (memoria de programa o

RAM).

¿Para que pueden servir los punteros?

Esta es la pregunta que puede alborotar a mas de un programador de C. Sirve

para muchísimas cosas:

Acceso a la memoria RAM del PIC.

Ahorrar memoria RAM.

Modificar más de una variable dentro de una función (y por consiguiente

devolver mas de un valor)

En arreglos y cadenas strings (arrays, matrices) juega un papel

importantísimo.

Permite crear tablas con montones de datos (en los PIC que soporten

acceso a la memoria de programa).

En un ordenador se amplía el abanico de opciones.

Más abajo veremos detalladamente como hacer todo esto.

Page 19: 78888821-Curso-Ccs-en-PDF.pdf

¿Como funcionan los punteros?

Para entender el uso de estas variables especiales hay que comprender bien

un concepto:

Cuando se crea una variable en CCS (llamado registro en ensamblador), el

compilador reserva un espacio de memoria cuyo tamaño varia de acuerdo al

tipo de dato.

Como todo en el mundo electrónico/digital, está basado en 2 cosas:

El registro: es la casilla donde se almacena el dato.

La dirección del registro: es la posición en la memoria donde está

alojado el registro.

así pues tenemos 2 elementos diferentes pero que se relacionan.

Conociendo la dirección del registro o variable y pudiéndolo manejar nos da un

poderosa herramienta para agilizar/simplificar nuestros programas.

¿Como podemos acceder a la dirección de una variable?

En CCS se hace a través del operador &. Veamos un ejemplo:

Ejemplo1:

#include <18F4550.h>

#use delay (clock=4000000)

void main(){

int t,k;

t=5;

k= &t;

delay_cycles (1);

}

al simular en el MPLAB tenemos:

Page 20: 78888821-Curso-Ccs-en-PDF.pdf

Cuando detenemos en delay_cycles(1) vemos que en k se guarda la dirección

de la variable t, ¿y que guarda t? guarda el número 5. todo se realiza usando

memoria RAM ó el registro de propósito general GPR.

Vamos a cambiar ligeramente el código. Usemos 3 variables tipo entero (int):

#include <18F4550.h>

#use delay (clock=4000000)

void main(){

int k,l,m;

int t,u,v;

t=0xfa; u=0xfb; v=0xfc;

k= &t; l= &u; m= &v;

delay_cycles(1);

}

Se repite lo mismo, el resultado de las direcciones en k, l y m son contiguas.

Pero... ¿por que?

Page 21: 78888821-Curso-Ccs-en-PDF.pdf

Para responder esta pregunta vamos a cambiar el código otra vez, declarando

los 3 tipos de registros conocidos, int, long y float:

#include <18F4550.h>

#use delay (clock=4000000)

void main(){

int k,l,m,n;

int t;

long u;

float v;

int z;

t=0xfa; z=0xff; u=0xfffa; v=3.45000000;

k= &t; l= &u; m= &v; n=&z;

delay_cycles(1);

}

la simulación:

Observa que las direcciones de t, u y v saltan. ¿Por que?

Dependiendo del tipo de dato se consume >= 1 byte de memoria. En el caso de

t es un entero, y los enteros ocupan 1 byte (0..255). u es un dato "entero

largo", ocupa dos bytes (0..65535) v es un dato "coma flotante", con parte

fraccionaria en el sistema decimal y toma 4 bytes de memoria (32 bits)

en t tenemos una dirección que ocupa un byte [0xA]

en u tenemos una dirección que ocupa 2 byte [0xB - 0xC]

en v tenemos una dirección que ocupa 4 bytes [0xD - 0x10]

Page 22: 78888821-Curso-Ccs-en-PDF.pdf

Probando punteros, primera parte

Siempre que se declare una variable puntero, al momento de usarlo se debe

especificar la dirección de apuntamiento de la variable normal, porque entonces

no se puede guardar un dato sino sabemos donde lo vamos a guardar. (Es

obvio pero es cierto)

Esto quiere decir que se le debe pasar el número por valor de la dirección de la

variable normal. Recordemos que:

Pasar un dato por valor: se copia el dato de una variable a otra.

Pasar un dato por referencia: se mueve/modifica el dato en la misma

variable.

Variable normal: la variable que normalmente usamos.

Variable puntero: es la variable especial que estamos estudiando.

Veamos un ejemplo sencillo usando punteros:

#include <18F4550.h>

#use delay (clock=4000000)

//*******************************

void main (){

int k; // variable normal

int *p; // la variable puntero

k=0xfa; // k <- 0xfa

*p=0x5;

delay_cycles (1);

}

Dentro del código reconocemos de inmediato quien es el puntero: el que tiene

el símbolo * debe ir antes de la letra p y sin separación:

*p así es como se debe declarar.

si nos vamos a MPLAB-SIM, y trazamos hasta delay_cycles(1) vemos en la

ventana LOCAL:

Page 23: 78888821-Curso-Ccs-en-PDF.pdf

pero... ¡no aparece nada en p! ¿Por que?

Es simple: porque no fijamos una dirección que apuntara p, y esto es muy

importante saberlo, era lo que se decía al inicio de este artículo. Vamos a darle

la dirección de k:

#include <18F4550.h>

#use delay(clock=4000000)

//*******************************

void main(){

int k; // variable normal

int *p; // la variable puntero

p=&k; // dirección de k copiada a p

k=0xfa; // k <- 0xfa

*p=0x5; // k <- 0x5

delay_cycles(1);

}

el resultado:

Ahora si funciona correctamente el código. Si ven la línea:

p=&k; // dirección de k copiada a p

Podran observar que se usa el puntero sin el *. Esto significa que se guardará

allí una dirección y el compilador lo interpreta de esa manera.

Page 24: 78888821-Curso-Ccs-en-PDF.pdf

Y con esta línea:

*p=0x5; // k <- 0x5

Se está modificando el contenido de k, (indirectamente)

Otro detalle a tomar en cuenta es que para apuntar cierto tipos de datos, es

que se debe declarar al apuntador con el mismo tipo de datos:

int k; // si queremos apuntar a k

int *p; // p debe ser tipo int

char c; // si queremos apuntar a c

char *p // p debe ser tipo char

no quiere decir que el tipo de datos que contendrá el puntero sea de ese tipo

de datos, el puntero siempre soportará números enteros positivos, en realidad

esto ya es a nivel interno del compilador.

Un ejemplo más:

En este código hay 2 punteros y 3 variables normales:

#include <18F4550.h>

#use delay(clock=4000000)

//*******************************

void main(){

int i; // variable normal

int *p; // la variable puntero

int j;

int *q;

int k;

//

p=&i; // dirección de i copiada a p

q=&j;

//

i=0xfa; // i <- 0xfa

Page 25: 78888821-Curso-Ccs-en-PDF.pdf

j=0x11;

k=0x22;

//

*p=0x5; // i <- 0x5

*q=0x33;

delay_cycles(1);

}

Entre i, p hay 1 byte -> i ocupa 1 byte.

Entre p, j hay 2 bytes -> puntero p ocupa 2 bytes.

Entre j, q hay 1 byte -> j ocupa 1 byte

Entre q, k hay 2 bytes -> puntero q ocupa 2 bytes.

Modificando el código para que i sea del tipo float:

#include <18F4550.h>

#use delay(clock=4000000)

//*******************************

void main(){

float i; // variable normal

float *p; // la variable puntero //

long j;

long *q;

int k;

//

i=2.51; // i <- 0xfa

j=0x11;

k=0x22;

//

Page 26: 78888821-Curso-Ccs-en-PDF.pdf

p=&i; // dirección de i copiada a p

q=&j; //

*p=3.99; // i <- 0x5

*q=0x33;

delay_cycles(1);

}

Entre i, p hay 4 bytes -> i ocupa 4 bytes.

Entre p, j hay 2 bytes -> puntero p ocupa 2 bytes.

Entre j, q hay 2 bytes -> j ocupa 2 bytes.

Entre q, k hay 2 bytes -> puntero q ocupa 2 bytes.

En ambos casos a pesar que cambiamos el tipo de declaración de los

punteros, se mantienen en 2 bytes, eso quiere decir que para el compilador el

tamaño de un puntero es de 2 bytes. No confundir con el tipo de datos a

direccionar, pues eso es otra cosa.

Vamos con otro ejemplo. Supongamos que i sea del tipo float (4 bytes) pero su

apuntador lo declaramos como int (1 byte):

#include <18F4550.h>

#use delay(clock=4000000)

//*******************************

void main(){

float i; // variable normal

int *p; // la variable puntero

long j;

long *q;

Page 27: 78888821-Curso-Ccs-en-PDF.pdf

int k;

//

i=2.51; // i <- 0xfa

j=0x11;

k=0x22;

//

p=&i; // dirección de i copiada a p

q=&j;

//

*p=3.99; // i <- 0x5

*q=0x33;

delay_cycles(1);

}

Noten que el nuevo valor de i no corresponde con el valor que indirectamente

le dimos con el apuntador.

¿Por que? Porque declaramos a ese apuntador como entero (int) y con ello le

estamos diciendo al compilador que reserve para p 1 byte de dirección en vez

de 4 bytes que son los que se necesitan y por eso ocurre ese truncamiento y

da ese valor extraño.

Para corregir esto, se declara a p del MISMO tipo de dato de i:

#include <18F4550.h>

#use delay(clock=4000000)

//*******************************

void main(){

float i; // variable normal

Page 28: 78888821-Curso-Ccs-en-PDF.pdf

float *p; // la variable puntero

long j;

long *q;

int k;

//

i=2.51; // i <- 0xfa

j=0x11;

k=0x22;

//

p=&i; // dirección de i copiada a p

q=&j;

//

*p=3.99; // i <- 0x5

*q=0x33;

delay_cycles(1);

}

Aquí se lee que está correcto el resultado.

Nota: los punteros tiene un máximo de 2 bytes para almacenar direcciones y el

CCS sigue la misma normativa. Hay una directiva llamada #device xxxxxxx

Page 29: 78888821-Curso-Ccs-en-PDF.pdf

con 4 modos de selección: CCS2,CCS3,CCS4 y ANSI. Con CCS2 y CCS3 el

tamaño (size) del puntero es de 1 byte en partes de 14, 16 bits y con CCS4

(modo por defecto) el size es de 2 bytes.

Probando punteros, segunda parte

Analizando nuevamente lo hablado referente al size de los punteros en CCS, y

en un intento de explicar que el tipo de dato y el tamaño del apuntado son 2

cosas distintas, vamos hacer un ejemplo donde se verá claramente. Para ello

vamos a usar una directiva llamada #locate, sobre la que la ayuda del

compilador reza así:

#LOCATE works like #BYTE however in addition it prevents C from using the

área

bueno esto quiere decir que la variable normal la puedo alojar en cualquier

dirección de la RAM (dentro de ciertos limites). Algo así como si en

ensamblador pusiéramos:

variable_normal EQU 0xNNNN

Esto nos servirá porque sería como manipular el contenido de un puntero pero

en tiempo de diseño

Page 30: 78888821-Curso-Ccs-en-PDF.pdf

#include <18F4550.h>

#use delay(clock=4000000)

//*********************************

int dato=0xaa; // declaramos dato (GPR) y lo cargamos con 0xAA

#locate dato = 0xff

// le decimos al compilador que dato estará en la dirección 0xFF

//del área de registro de propósito general, traducido, en la RAM del PIC

void main(){

int *p; // declaramos un puntero como entero (igual que dato)

int t; // otra variable normal

p=&dato; // inicializamos al puntero

*p=0xbb; // dato <- 0xBB

delay_cycles(1); // un nop

}

Page 31: 78888821-Curso-Ccs-en-PDF.pdf

Fíjense que el puntero p ocupa 2 bytes a pesar que está declarado como int (1

byte).

Vamos a modificar este ejemplo pero usando float (4 bytes) y colocando el

GPR en la dirección 0xAF

Page 32: 78888821-Curso-Ccs-en-PDF.pdf

Observen que el puntero p se mantuvo en 2 bytes siendo éste declarado como

float.

Supongamos un ejemplo para el PIC18F4550, en el que tenemos una memoria

de datos que llega hasta 0x7FF (Pág. 66 de su hoja de datos). Para que

funcione 0x7FF debe ser el 4 byte para un float, entonces

float dato=1.23456789;

#locate dato = 0x7FB

...

Page 33: 78888821-Curso-Ccs-en-PDF.pdf

Si que funcionó. Pero, ¿que pasa si asignamos el dato a 0x800?

Allí vemos que el puntero se cargó bien, pero el MPLAB-SIM delata el

desbordamiento, ¿Por que? Es que a partir de allí no hay memoria de datos y

las direcciones se deberían leer como puros 0x0 a pesar que compiló bien,

(similarmente en programas de computadoras pueden ocurrir los lazos infinitos

popularmente llamado ‘se colgó la máquina’)

Punteros en funciones

Todo lo que hagamos en CCS se hace a través de funciones o procedimientos,

desde el punto de vista matemático una función se define así:

Una función es una relación entre dos variables numéricas, habitualmente las

denominamos x e y; a una de ellas la llamamos variable dependiente pues

depende de los valores de la otra para su valor, suele ser la y; a la otra por

tanto se la denomina variable independiente y suele ser la x. Pero además,

para que una relación sea función, a cada valor de la variable independiente le

corresponde uno o ningún valor de la variable dependiente, no le pueden

corresponder dos o más valores.

Aplicándolo a la programación, significa que podemos tener varios argumentos

o parámetros de entrada, pero solo tendremos un dato de salida. Y eso no es

todo, en C una función pasa los argumentos por valor. ¿que quiere decir esto?

Page 34: 78888821-Curso-Ccs-en-PDF.pdf

Que cuando llamemos a la función y le pasemos el dato como argumento, ésta

copiará ese dato en su propia función sin alterar la variable original. veamos un

ejemplo:

#include <18F4550.h>

#use delay(clock=4000000)

//*********************************

int mi_funcion(int argumento1, argumento2){

delay_cycles (1);

return (argumento1 + argumento2);

}

//*******************************

void main(){

int k,l,resultado;

k=5; L=2;

resultado = mi_funcion(k,L);

delay_cycles(1);

}

Noten que cuando llamo a mi_funcion, se copia el contenido de k ->

argumento1 y L -> argumento2, luego hace la suma y regresa un dato con el

resultado de la suma. k y L se quedan con el mismo valor anterior.

¿y si queremos cambiar esas variables como se hace?

Page 35: 78888821-Curso-Ccs-en-PDF.pdf

Bueno seguro que alguien llegará y colocará a k y L como globales y entonces

así se puede modificar en cualquier lado. Pero si la variable es local, dentro de

main (), no se puede modificar fuera de main ()...a menos que usemos

punteros. ¿y como se haría eso?

Simple: se haría pasando el argumento a la función como referencia, haciendo

referencia a la dirección, es decir lo que se pasará a la función es la dirección

de k, L entonces allí si se puede modificar a gusto. Un ejemplo:

#include <18F4550.h>

#use delay(clock=4000000)

//*********************************

int mi_funcion(int argumento1, argumento2, *la_k, *la_L){

delay_cycles (1);

*la_k=0xFF; *la_L=0xAF;

return (argumento1 + argumento2);

}

//*******************************

void main(){

int k,l,resultado;

k=5; l=2;

resultado = mi_funcion(k,l,&k,&l);

delay_cycles (1);

}

Punteros en Arrays

Como sabrán los arrays son arreglos de datos que van en direcciones

consecutivas, es decir, uno detrás del otro. Un ejemplo de ello:

char cadena[7]={'T','o','d','o','P','i','c'};

Si lo probamos en un código:

#include <18F4550.h>

#use delay(clock=4000000)

Page 36: 78888821-Curso-Ccs-en-PDF.pdf

//*********************************

char cadena[7]={'T','o','d','o','P','i','c'};

void main(){

char c;

int t;

for(t=0;t<7;t++){

c=cadena[t];

}

delay_cycles(1);

}

Page 37: 78888821-Curso-Ccs-en-PDF.pdf

Se pueden usar punteros en el ejemplo anterior. Veamos como:

Declarando un puntero como char:

char c, *p;

lo inicializamos (le damos la dirección del primer elemento del array):

p=&cadena[0];

luego hacemos un barrido de direcciones para tomar el contenido de cada

elemento y guardarlo en c

for (t=0;t<7;t++){

c= *p + t;

}

Pero ese programa tiene 2 errores y no funcionará:

El primer error es que según la precedencia del operador primero está el

puntero y luego viene la suma, y así estaríamos sumando direcciones que

varían, la solución es usar *(p+i)

¿Y que es eso de que varían? Pues que cuando se recorre el arrays con el

puntero, este debe ir sumando direcciones, pero direcciones de números

constantes, es decir, si el tipo de datos es 1 byte, entonces el puntero debe

acumular números enteros de 1 byte en 1 byte

Si el tipo de datos es long (entero largo) entonces el puntero debe ir sumando

direcciones de 2 bytes en 2 bytes. ¿Porque digo esto? Es que p quedará fijo (la

dirección) y el truco está en desplazar al puntero tantas posiciones sea el size

del tipo de dato. Este sería el segundo error y la solución es la misma: *(p+i)

Vamos a cambiar ese ejemplo por números long para que se entienda

Page 38: 78888821-Curso-Ccs-en-PDF.pdf

#include <18F4550.h>

#use delay(clock=4000000)

//*********************************

long cadena[7]={1000,2000,3000,4000,5000,6000,7000};

void main(){

long c, *p;

int t;

p=&cadena[0];

for(t=0;t<7;t++){

c= *p + t;

}

delay_cycles(1);

}

Page 39: 78888821-Curso-Ccs-en-PDF.pdf

Fíjense que p queda inmutable, y lo que hace el programa es contenido[0] + t.

¡Grave error!

Arreglando el programa con *(p+t)

Page 40: 78888821-Curso-Ccs-en-PDF.pdf

Con esto estamos garantizando que el puntero se moverá de 2 bytes en 2

bytes, es decir

*(p+t) =

0x5 + 0x2 (desplazamiento de 2 byte)-> dame el contenido de la dirección 0x5

0x5 + 1x2 " -> dame el contenido de la dirección 0x7

0x5 + 2x2 " -> dame el contenido de la dirección 0x9

0x5 + 3x2 " -> dame el contenido de la dirección 0xA

...

Page 41: 78888821-Curso-Ccs-en-PDF.pdf

Noten que la suma se realiza no intervalos de t sino en intervalos del ancho del

tipo de dato. Pero...¿esto no es lo mismo que se hizo en el código del inicio del

artículo?

O sea que ¿ c = cadena[t]; es igual a c = *(p + t) cuando p = &cadena[0]; ?

Pues si, acabamos de ver un array al desnudo, como funciona en realidad, no

es mas que un puntero escondido a nuestra vista. Solo que para hacer fácil la

programación el compilador lo acepta de esta manera. Si p es un puntero -> p

= cadena (para el primer índice del arreglo) es totalmente válido, se acepta que

cadena es un puntero constante, también se podría llamar un puntero nulo (ya

que no se ve y tampoco se puede modificar).

Ejemplos validos:

cadena [0] = *cadena

cadena [2] = *(cadena + 2)

cadena = *(cadena + i)

Nota: el operador () es el primero que atiende el compilador, antes que al resto.

Acomodando el código original, el que tenía la cadena de caracteres:

#include <18F4550.h>

#use delay(clock=4000000)

//*********************************

char cadena[7]={'T','o','d','o','P','i','c'};

void main(){

char c, *p;

int t;

p=cadena;

for(t=0;t<7;t++){

c=*(p+t);

}

delay_cycles(1);

}

Page 42: 78888821-Curso-Ccs-en-PDF.pdf
Page 43: 78888821-Curso-Ccs-en-PDF.pdf

CCS - Funciones

Las funciones son los bloques básicos con los que construimos un programa en CCS. Además de la función main() que veremos enseguida, un programa CCS tendrá seguramente varias funciones más, conteniendo cada una un bloque de instrucciones que realizan una tarea determinada.

Funciones

Las funciones tienen la siguiente forma:

nombre_de_la_funcion() { instruccion; instruccion; . . instruccion; }

Para evitar que surjan errores o avisos (warnings) al compilar nuestros programas, debemos declarar las funciones antes de utilizarlas.

Prototipos

Existen dos formas de decirle al compilador CCS que tipo de valor devolverá nuestra función. La forma general es la siguiente:

tipo nombre_de_funcion();

donde tipo es cualquiera de los tipos de variables soportados por CCS. Al igual que cualquier instrucción de CCS, la línea debe termina con ; (punto y coma).

El siguiente ejemplo declara la función ejemplo() que devuelve como resultado un valor del tipo long:

long ejemplo();

Parámetros

Además de determinar el tipo de resultado que devolverá la función, en el prototipo podemos especificar que parámetros recibirá, y de que tipo serán. La forma de hacerlo es la siguiente:

tipo nombre_de_funcion(tipo var1, tipo var2, ..., tipo varN);

La diferencia con el caso anterior es que se han incluido dentro de los () una serie de nombres de variables (var1, var2, ..., varN), cada una asociado a un tipo en particular.

Page 44: 78888821-Curso-Ccs-en-PDF.pdf

Supongamos que queremos crear una función que lleve a cabo la suma de dos de tipo int, que le son pasados como argumentos, y nos devuelva el resultado en formato double. Deberíamos escribir así su prototipo:

double suma(int a, int b);

donde a y b son los valores a sumar. El llamado a la función se puede hacer de la siguiente manera:

int a, b;double resultado;a = 10;b = 250;resultado = suma (a, b);

resultado contendrá el valor "300".

Return

La forma en que se asigna en la función el valor que esta debe devolver es mediante la instrucción return.

Veámoslo con el ejemplo de la función suma vista mas arriba. La función podría ser como sigue:

double suma(int a, int b){ double auxiliar; auxiliar = (double) (a * b ); return auxiliar;}

Otra forma, mas corta, de escribir la misma función es la siguiente:

double suma(int a, int b){ return (double) a * b; }

Void

void significa que la función no devolverá ningún parámetro. Supongamos que la función ejemplo() no debe regresar ningún valor luego de ser llamada. Suprototipo debería ser como sigue:

void ejemplo();

Además, podemos usar void para indicar que la función no recibe parámetros:

void ejemplo2(void);

Page 45: 78888821-Curso-Ccs-en-PDF.pdf

en el ejemplo, la función ejemplo2() no recibe parámetros, ni devuelve ningún valor.

La función main()

Como hemos visto, el lenguaje C permite la utilización de funciones. Pero hay una función especial, llamada main () que obligatoriamente debe estar presente, y es el punto de entrada a todo programa en C que escribamos.

La función main () tiene la siguiente forma:

main() { instruccion; instruccion; . . instruccion; }

donde instruccion; puede ser cualquier instrucción válida del CCS o una llamada a otra función.

CCS - Uso de LCDs alfanuméricos.

En CCS no disponemos de instrucciones específicas para el manejo de pantallas LCD. Sin embargo, nada impide que escribamos funciones que sean capaces de inicializar, escribir o borrar (e incluso leer) los datos de estas pantallas. El hecho de que la mayoría de los módulos LCD estén construidos en base al controlador Hitachi HD44780.

LCD.C

Para ponernos las cosas más fáciles, dentro de la carpeta "drivers" de CCS se encuentra un archivo llamado LCD.C, que si lo incluimos en nuestro proyecto, nos proveerá de las funciones necesarias. Sin embargo, LCD.C tiene algunas limitaciones: tal como está, solo funciona si conectamos nuestro LCD en el puerto D (o B, con una modificación menor).

Para incluirlo en nuestro programa, basta con hacer lo siguiente:

#INCLUDE "lcd.c"

en cuanto al hardware,las líneas de datos deben conectarse de la siguiente manera:

PORT.D0 -> enable PORT.D1 -> rs

Page 46: 78888821-Curso-Ccs-en-PDF.pdf

PORT.D2 -> rw PORT.D4 -> D4 PORT.D5 -> D5 PORT.D6 -> D6 PORT.D7 -> D7

Como puede verse, se trata de una comunicación con solo 4 bits de datos. Más adelante veremos como modificar este archivo para que se pueda emplear con el LCD en otro puerto y/o con otra asignación de pines. El siguiente es el contenido del archivo LCD.C tal como es provisto por CCS.

///////////////////////////////////////////////////////////////////////////////////////////////////////////// LCD.C //////// Driver for common LCD modules //////// //////// lcd_init() Must be called before any other function. //////// //////// lcd_putc(c) Will display c on the next position of the LCD. //////// The following have special meaning: //////// \f Clear display //////// \n Go to start of second line //////// \b Move back one position //////// //////// lcd_gotoxy(x,y) Set write position on LCD (upper left is 1,1) //////// //////// lcd_getc(x,y) Returns character at position x,y on LCD //////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// (C) Copyright 1996,2003 Custom Computer Services //////// This source code may only be used by licensed users of the CCS C //////// compiler. This source code may only be distributed to other //////// licensed users of the CCS C compiler. No other use, reproduction //////// or distribution is permitted without written permission. //////// Derivative programs created using this software in object code //////// form are not restricted in any way. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// As defined in the following structure the pin connection is as follows:// D0 enable// D1 rs// D2 rw// D4 D4// D5 D5// D6 D6// D7 D7//// LCD pins D0-D3 are not used and PIC D3 is not used.//// Un-comment the following define to use port B// #define use_portb_lcd TRUE

Page 47: 78888821-Curso-Ccs-en-PDF.pdf

////struct lcd_pin_map { // This structure is overlayed BOOLEAN enable; // on to an I/O port to gain BOOLEAN rs; // access to the LCD pins. BOOLEAN rw; // The bits are allocated from BOOLEAN unused; // low order up. ENABLE will int data : 4; // be pin B0. } lcd;//#if defined(__PCH__)#if defined use_portb_lcd #byte lcd = 0xF81 // This puts the entire structure#else #byte lcd = 0xF83 // This puts the entire structure#endif#else#if defined use_portb_lcd #byte lcd = 6 // on to port B (at address 6)#else #byte lcd = 8 // on to port D (at address 8)#endif#endif//#if defined use_portb_lcd #define set_tris_lcd(x) set_tris_b(x)#else #define set_tris_lcd(x) set_tris_d(x)#endif//#define lcd_type 2 // 0=5x7, 1=5x10, 2=2 lines#define lcd_line_two 0x40 // LCD RAM address for the second line//BYTE const LCD_INIT_STRING[4] = {0x20 | (lcd_type << 2), 0xc, 1, 6}; // These bytes need to be sent to the LCD // to start it up.// // The following are used for setting // the I/O port direction register.struct lcd_pin_map const LCD_WRITE = {0,0,0,0,0}; // For write mode all pins are outstruct lcd_pin_map const LCD_READ = {0,0,0,0,15}; // For read mode data pins are in//BYTE lcd_read_byte() { BYTE low,high; set_tris_lcd(LCD_READ); lcd.rw = 1; delay_cycles(1); lcd.enable = 1;

Page 48: 78888821-Curso-Ccs-en-PDF.pdf

delay_cycles(1); high = lcd.data; lcd.enable = 0; delay_cycles(1); lcd.enable = 1; delay_us(1); low = lcd.data; lcd.enable = 0; set_tris_lcd(LCD_WRITE); return( (high<<4) | low);}//void lcd_send_nibble( BYTE n ) { lcd.data = n; delay_cycles(1); lcd.enable = 1; delay_us(2); lcd.enable = 0;}//void lcd_send_byte( BYTE address, BYTE n ) { lcd.rs = 0; while ( bit_test(lcd_read_byte(),7) ) ; lcd.rs = address; delay_cycles(1); lcd.rw = 0; delay_cycles(1); lcd.enable = 0; lcd_send_nibble(n >> 4); lcd_send_nibble(n & 0xf);}//void lcd_init() { BYTE i; set_tris_lcd(LCD_WRITE); lcd.rs = 0; lcd.rw = 0; lcd.enable = 0; delay_ms(15); for(i=1;i<=3;++i) { lcd_send_nibble(3); delay_ms(5); } lcd_send_nibble(2); for(i=0;i<=3;++i) lcd_send_byte(0,LCD_INIT_STRING[i]);}//void lcd_gotoxy( BYTE x, BYTE y) { BYTE address;

Page 49: 78888821-Curso-Ccs-en-PDF.pdf

if(y!=1) address=lcd_line_two; else address=0; address+=x-1; lcd_send_byte(0,0x80|address);}//void lcd_putc( char c) { switch (c) { case '\f' : lcd_send_byte(0,1); delay_ms(2); break; case '\n' : lcd_gotoxy(1,2); break; case '\b' : lcd_send_byte(0,0x10); break;

default : lcd_send_byte(1,c); break; }}//char lcd_getc( BYTE x, BYTE y) { char value; lcd_gotoxy(x,y); while ( bit_test(lcd_read_byte(),7) ); // wait until busy flag is low lcd.rs=1; value = lcd_read_byte(); lcd.rs=0; return(value);}

Funciones en LCD.C

Hay cuatro funciones implementadas dentro de LCD.C:

lcd_init()

Esta función es la encargada de enviar los comandos de inicializaciónnecesarios al LCD. Es obligatorio ejecutar esta función antes de utilizar el display para escribir sobre él. No recibe ni devuelve valores de ningún tipo. Su forma de uso es tan simple como:

lcd_init ();

y listo.

lcd_putc()

Esta seguramente será la función que mas emplearemos. Es la que se encarga de escribir nuestro mensaje en la pantalla. No devuelve valores, pero si

Page 50: 78888821-Curso-Ccs-en-PDF.pdf

(obviamente) los recibe. La forma de uso es muy simple. Basta con llamarla, pansandole como parámetro una variable o constante tipo char, y la función se encargará de desplegar su contenido sobre el display.

Lcd_putc ("uControl.com.ar");

Produce la siguiente salida sobre el display:

Por supuesto, no debemos olvidar de inicializar previamente el display. Además, lcd_putc() reconoce los siguientes comandos que pueden ser enviados en el texto a mostrar:

\f -> Borra la pantalla. \n -> Salta al comienzo de la segunda línea. \b -> Retrocede una posición.

Esto quiere decir que si modificamos nuestro código para que quede así:

Lcd_putc ("uControl.com.ar\n LCD en CCS");

obtendremos el siguiente texto en el LCD:

Por último, si queremos borrar el contenido de la pantalla, bastará con lo siguiente:

Lcd_putc ("\f");

que dejará nuestro display completamente limpio:

Page 51: 78888821-Curso-Ccs-en-PDF.pdf

lcd_gotoxy(x,y)

Esta es la función que nos permite colocar el cursor en la parte que deseemos de la pantalla. Recibe dos parámetros, ambos de tipo byte. El primero de ellos indica la columna en la que aparecerá el primer carácter del texto, y el segundo se refiere a la fila en que lo hará. El siguiente código ejemplifica el uso de lcd_gotoxy(x,y):

Lcd_putc ("uControl.com.ar"); lcd_gotoxy (5,2); //salto a columna 4, fila 2 Lcd_putc ( "LCD en CCS");

hace lo siguiente:

Importante: Tanto las filas como las columnas se cuentan a partir de 1.

lcd_getc(x,y)

Esta función recibe como parámetros la columna' y la fila (ambos de tipo byte) de la que deseamos conocer el contenido, y nos devuelve un charcon el contenido. Su uso no podría ser más sencillo:

char a;a = lcd_getc(5,2);

Si el display conserva el texto de la imagen anterior, la variable a contendrá el valor "L".

Importante: Tanto las filas como las columnas se cuentan a partir de 1.

Modificando LCD.C

Por supuesto, en la mayoría de los casos la conexión entre el microcontroladory el display LCD no coincidirá con la especificada en el archivo LCD.C provistopor CCS. Pero eso no quiere decir que debamos rediseñar nuestro proyecto, ni que sea imposible modificar la configuración de LCD.C. como puede verse, dentro del código de LCD.C se define una estructura de datos que es la encargada de contener la distribución de los pines a utilizar, junto a la función que desempeñará. Es el siguiente trozo de código:

struct lcd_pin_map { // This structure is overlayed BOOLEAN enable; // on to an I/O port to gain BOOLEAN rs; // access to the LCD pins. BOOLEAN rw; // The bits are allocated from

Page 52: 78888821-Curso-Ccs-en-PDF.pdf

BOOLEAN unused; // low order up. ENABLE will int data : 4; // be pin B0. } lcd;

Supongamos que tenemos un display conectado de la siguiente manera:

PORT.B2 -> enable PORT.B3 -> rs PORT.B4 -> D4 PORT.B5 -> D5 PORT.B6 -> D6 PORT.B7 -> D7

Notar que no estamos usando el pin RW del display, que estará permanentemente conectado a GND. La estructura debería quedar así:

struct lcd_pin_map { BOOLEAN unused1; // RB0 BOOLEAN unused2; // RB1 BOOLEAN enable; // RB2 BOOLEAN rs; // RB3 int data : 4; // RB4-RB7} lcd;

Por supuesto, habrás notado que en lugar del puerto D estamos usando el puerto B, así que hay que quitar el comentario a la línea

// #define use_portb_lcd TRUE

para que quede así:

#define use_portb_lcd TRUE //LCD conectado al puerto b.

Y como no estamos usando la línea RW del LCD, debemos quitar las dos tres líneas de código en la que se hace referencia a ella. El listado siguientecorresponde al archivo LCD.C con todas las modificaciones mencionadas, más algunas modificaciones en los #INCLUDE del principio, que no tienen sentido mantener ya que al personalizar el archivo nunca se van a dar algunas de las condiciones contempladas allí. También hemos quitado el código de la función lcd_getc( x, y) ya que al estar RW conectado de forma permanente a GND, será imposible leer caracteres del display.

///////////////////////////////////////////////////////////////////////////// LCD.C modificada por uControl.com.ar ///////////////////////////////////////////////////////////////////////////// B0 // B1 // B2 E

Page 53: 78888821-Curso-Ccs-en-PDF.pdf

// B3 RS// B4 D4// B5 D5// B6 D6// B7 D7// (Sin 'RW')//// Funciones soportadas:// lcd_init()// lcd_gotoxy( BYTE col, BYTE fila)// lcd_putc( char c)// \f Clear display // \n Go to start of second line // \b Move back one position/////////////////////////////////////////////////////////////////////////////#define use_portb_lcd TRUE //LCD conectado al puerto b.//struct lcd_pin_map { BOOLEAN unused1; // RB0 BOOLEAN unused2; // RB1 BOOLEAN enable; // RB2 BOOLEAN rs; // RB3 int data : 4; // RB4-RB7} lcd;//#byte lcd = 0xF81 // Dirección de la estructura "lcd".#byte lcd = 6 // Dirección del puerto B.#define set_tris_lcd(x) set_tris_b(x)#define lcd_type 2 // Tipo de LCD: 0=5x7, 1=5x10, 2=2 líneas#define lcd_line_two 0x40 // Dirección de la LCD RAM para la 2da. línea////Defino la cadena de inicialización del LCD.BYTE const LCD_INIT_STRING[4] = {0x20 | (lcd_type << 2), 0xc, 1, 6};////Configuro el estado de cada pin para lectura y escritura:struct lcd_pin_map const LCD_WRITE = {0,0,0,0,0}; // Escribir.struct lcd_pin_map const LCD_READ = {0,0,0,0,15}; // Leer.////Funciones:BYTE lcd_read_byte() { BYTE low,high; set_tris_lcd(LCD_READ); delay_cycles(1); lcd.enable = 1; delay_cycles(1); high = lcd.data; lcd.enable = 0; delay_cycles(1); lcd.enable = 1;

Page 54: 78888821-Curso-Ccs-en-PDF.pdf

delay_us(1); low = lcd.data; lcd.enable = 0; set_tris_lcd(LCD_WRITE); return( (high<<4) | low);}//void lcd_send_nibble( BYTE n ) { lcd.data = n; delay_cycles(1); lcd.enable = 1; delay_us(2); lcd.enable = 0;}//void lcd_send_byte( BYTE address, BYTE n ) { lcd.rs = 0; while ( bit_test(lcd_read_byte(),7) ) ; lcd.rs = address; delay_cycles(1); delay_cycles(1); lcd.enable = 0; lcd_send_nibble(n >> 4); lcd_send_nibble(n & 0xf);}//void lcd_init() { BYTE i; set_tris_lcd(LCD_WRITE); lcd.rs = 0; lcd.enable = 0; delay_ms(15); for(i=1;i<=3;++i) { lcd_send_nibble(3); delay_ms(5); } lcd_send_nibble(2); for(i=0;i<=3;++i) lcd_send_byte(0,LCD_INIT_STRING[i]);}//void lcd_gotoxy( BYTE x, BYTE y) { BYTE address; if(y!=1) address=lcd_line_two; else address=0; address+=x-1; lcd_send_byte(0,0x80|address);}

Page 55: 78888821-Curso-Ccs-en-PDF.pdf

//void lcd_putc( char c) { switch (c) { case '\f' : lcd_send_byte(0,1); delay_ms(2); break; case '\n' : lcd_gotoxy(1,2); break; case '\b' : lcd_send_byte(0,0x10); break; default : lcd_send_byte(1,c); break; }}

Libreria de gráficos para GLCD K0108 en CCS

El compilador CCS proporciona una librería capaz de dibujar primitivas sobre varios modelos de displays LCD gráficos o GLCD (por Graphic Liquid Cristal Display). Hay versiones de esta librería para pantallas con diferentes controladores embebidos, como el Samsung KS0108 o el Toshiba T6963.

Pero a pesar de que pueden distribuirse libremente los trabajos que hagamos con ellas, no pueden compartirse los programas que las contengan a menos que la persona que los recibe también sea un usuario registrado de CCS.

Esto limita mucho su uso con fines educativos. De hecho, si quisiésemosexponer aquí un programa que grafique algo en un GLCD, estaríamos violando la licencia, ya que es muy posible que muchos de los lectores de uControl no hayan comprado el compilador.

Es por ello que nos hemos decidido a escribir una librería propia, que usaremos de ahora en más para nuestros proyectos.

La librería GLCD_K0108

Puedes descargar la libreria GLCD_K0108.C haciendo click aquí.

En las siguientes secciones iremos explicando cada una de sus partes.

IMPORTANTE: El trazado de líneas se basa en el Algoritmo de Bresenham, y las circunferencias se han resuelto mediante el "algoritmo del punto medio", que divide la circunferencia en 8 partes simétricas, evitando utilizar funciones como seno, coseno o potencias, que volverían muy lenta la tarea del trazado.

GLCD_limpiar (color)

Page 56: 78888821-Curso-Ccs-en-PDF.pdf

Esta es la función que "pinta" toda la pantalla con uno u otro color. Si recibe como parámetro un "1", la pintará completamente de negro. Si recibe un "0", la limpiará por completo.

Por supuesto, su mayor utilidad es la segunda alternativa.

Su funcionamiento también es muy sencillo, y se "apoya" en GLCD_envia BYTE () para escribir en el GLCD. Recorre ambas mitades del GLCD, página por página, de arriba hacia abajo, escribiendo "0x00" o "0xFF" según se haya elegido pintar o borrar.

(Ház clic sobre las imágenes para ampliarlas)

En la primer imágen, utilizando GLCD_limpiar(1);, la pantalla se pinta completamente de negro. En la segunda, mediante GLCD_limpiar(0);, se pinta completamente de blanco. Podemos usar esta función para limpiar la pantalla.

Puedes ver un ejemplo de uso de esta función aquí.

Page 57: 78888821-Curso-Ccs-en-PDF.pdf

GLCD_inicializa (modo)

Esta es la primera función de la librería que debe llamar nuestro programa.

Se encarga de inicializar el GLCD, y el parámetro "modo" determina si estará encendido (si recibe un "1") o apagado (si recibe un "0").

Puedes ver un ejemplo de uso de esta función aquí.

GLCD_punto(x, y, color)

Esta es la "primitiva gráfica" indispensable. A partir de GLCD_punto(x, y, color) escribiremos todas las funciones restantes.

Los parametros que recibe GLCD_punto(x, y, color) son:

x: un byte, es la coordenada "x" (horizontal), con valores válidos de 0 a 127 (izquierda a derecha).

y: un byte, es la coordenada "y" (vertical), con valores válidos de 0 a 63 (arriba a abajo)

color: un bit, "0" = apagado, "1" = encendido.

(Ház clic sobre la imágen para ampliarla)

Puedes ver un ejemplo de uso de esta función aquí.

GLCD_linea(x1, y1, x2, y2, color)

La línea también resulta indispensable a la hora de dibujar un gráfico.

Los parametros que recibe GLCD_linea(x1, y1, x2, y2, color) son:

Page 58: 78888821-Curso-Ccs-en-PDF.pdf

x1: un byte, es la coordenada "x" (horizontal) del primer extremo de la línea, con valores válidos de 0 a 127 (izquierda a derecha).

y1: un byte, es la coordenada "y" (vertical) del primer extremo de la línea, con valores válidos de 0 a 63 (arriba a abajo).

x2: un byte, es la coordenada "x" (horizontal) del segundo extremo de la línea, con valores válidos de 0 a 127 (izquierda a derecha).

y2: un byte, es la coordenada "y" (vertical) del segundo extremo de la línea, con valores válidos de 0 a 63 (arriba a abajo).

color: un bit, "0" = línea en blanco, "1" = línea en negro.

(Ház clic sobre las imágenes para ampliarlas)

Puedes ver un ejemplo de uso de esta función aquí.

GLCD_rectangulo(x1, y1, x2, y2, color)

Los rectángulos de dibujan (internamente) mediante cuatro llamadas a la función GLCD_linea.

Los parametros que recibe GLCD_rectangulo(x1, y1, x2, y2, color) son:

Page 59: 78888821-Curso-Ccs-en-PDF.pdf

x1: un byte, es la coordenada "x" (horizontal) de la esquina superior izquierda del rectángulo, con valores válidos de 0 a 127 (izquierda a derecha).

y1: un byte, es la coordenada "y" (vertical) de la esquina superior izquierda del rectángulo, con valores válidos de 0 a 63 (arriba a abajo).

x2: un byte, es la coordenada "x" (horizontal) de la esquina inferior derecha del rectángulo, con valores válidos de 0 a 127 (izquierda a derecha).

y2: un byte, es la coordenada "y" (vertical) de la esquina inferior derecha del rectángulo, con valores válidos de 0 a 63 (arriba a abajo).

color: un bit, "0" = rectángulo en blanco, "1" = rectángulo en negro.

(Ház clic sobre las imágenes para ampliarlas)

Puedes ver un ejemplo de uso de esta función aquí.

GLCD_caja(x1, y1, x2, y2, color)

Las "cajas" son rectángulos pintados en su interior con el mismo color que el borde exterior. También se dibujan (internamente) mediante llamadas a la función GLCD_linea.

Page 60: 78888821-Curso-Ccs-en-PDF.pdf

Los parametros que recibe GLCD_caja(x1, y1, x2, y2, color) son:

x1: un byte, es la coordenada "x" (horizontal) de la esquina superior izquierda del rectángulo, con valores válidos de 0 a 127 (izquierda a derecha).

y1: un byte, es la coordenada "y" (vertical) de la esquina superior izquierda del rectángulo, con valores válidos de 0 a 63 (arriba a abajo).

x2: un byte, es la coordenada "x" (horizontal) de la esquina inferior derecha del rectángulo, con valores válidos de 0 a 127 (izquierda a derecha).

y2: un byte, es la coordenada "y" (vertical) de la esquina inferior derecha del rectángulo, con valores válidos de 0 a 63 (arriba a abajo).

color: un bit, "0" = caja en blanco, "1" = caja en negro.

(Ház clic sobre las imágenes para ampliarlas)

Puedes ver un ejemplo de uso de esta función aquí.

GLCD_circulo(x1, y1, radio, color)

Esta es la función que dibuja un circulo. El interior del circulo permanece del color del fono. Estrictamente hablando, se dibuja solo la circunferencia.

Page 61: 78888821-Curso-Ccs-en-PDF.pdf

Los parametros que recibe GLCD_circulo(x1, y1, radio, color) son:

x1: un byte, es la coordenada "x" (horizontal) del centro del circulo, con valores válidos de 0 a 127 (izquierda a derecha).

y1: un byte, es la coordenada "y" (vertical) del centro del circulo, con valores válidos de 0 a 63 (arriba a abajo).

radio: un byte, es el radio de la circunferencia (en pixeles). color: un bit, "0" = circulo en blanco, "1" = circulo en negro.

(Ház clic sobre las imágenes para ampliarlas)

Puedes ver un ejemplo de uso de esta función aquí.

Definiciones, pines y otras yerbas

Para que la libreria pueda ser adaptada al proyecto que tienes en mente, hemos colocado los siguientes "#define" al comienzo de la misma, para determinar que pin del PIC has conectado a cada pin del GLCD:

//Pines a usar

Page 62: 78888821-Curso-Ccs-en-PDF.pdf

#define GLCD_CS1 PIN_E2#define GLCD_CS2 PIN_E1#define GLCD_DI PIN_C3#define GLCD_RW PIN_C2#define GLCD_E PIN_C1#define GLCD_RESET PIN_E0

Si tu circuito emplea pines diferentes a los del ejemplo para manejar el GLCD, deberás cambiar los valores que sea necesario.

El display está "partido" en dos mitades de 64x64 pixeles. Esto implica que al momento de escribir en el debemos seleccionar en cual de las dos mitades lo estamos haciendo. Para ello dispone de dos líneas de control (ver el pinout del GLCD en la sección correspondiente), llamadas CS1 y CS2. Asignaremos los valores de 0 y 1 a "GLCD_lado_CS1" y "GLCD_lado_CS2", respectivamente.

//Lados del GLCD#define GLCD_lado_CS1 0#define GLCD_lado_CS2 1

Tambien hemos definido un BYTE que guardará el dato que leyamos desde el GLCD:

BYTE GLCD_leeBYTE(int1 lado);

GLCD-K0108 en el PIC SIMULATOR IDE

Page 63: 78888821-Curso-Ccs-en-PDF.pdf

Veamos ahora las funciones internas básicas de la librería:

GLCD_enviaBYTE (lado, dato)

Esta función envía un byte a uno u otro lado del display. Como mencionamos antes, debemos seleccionar previamente, mediante la activación de CS1 o CS2, cual utilizaremos.

El parámetro (tipo int1) "lado" es el que define a que mitad del GLCD irá a parar el "dato".

El resto de la función no tiene ningún secreto. En ella, se realizan los siguientes pasos:

Se pone el GLCD en "modo escritura", poniendo en bajo GLCD_RW. Se coloca el dato en el puerto D del PIC durante 1 microsegundo. Se habilita el GLCD, poniendo en alto GLCD_E durante 2

microsegundos. Se deshabilita el GLCD, poniendo en bajo nuevamente GLCD_E Se vuelven a nivel bajo CS1 y CS2

GLCD_leeBYTE (lado)

Esta es la función que permite leer un byte desde el GLCD.

Igual que la anterior, recibe como parámetro la información que le indica en cual de las dos mitades está el dato a leer.

Realiza los siguientes pasos:

Se configura el puerto D como entrada. Se pone el GLCD en "modo lectura", poniendo en alto GLCD_RW. Se selecciona la mitad correspondiente, mediante CS1 y CS2. Se habilita el GLCD, poniendo en alto GLCD_E durante 2

microsegundos. Se guarda el dato y se deshabilita el GLCD, poniendo en bajo

nuevamente GLCD_E Se vuelven a nivel bajo CS1 y CS2

La función devuelve un BYTE con el dato leído.

GLCD_letra (carácter, color, fuente)

El presente artículo esta incompleto.

Page 64: 78888821-Curso-Ccs-en-PDF.pdf

En éste momento estamos trabajando en él, y en poco tiempo estará terminado.

El equipo de uControl.

GLCD con controlador Samsung KS0108

Por ahora, nuestra librería ofrece soporte para los GLCD de 128x64 píxeles, monocromáticos, que tienen un controlador Samsung KS0108 (o compatibles).

El que hemos usado en nuestras pruebas es el de la foto siguiente. Se trata de un ELWG12864-YYB-VN, con puntos negros sobre fondo verde, de la empresa Winstar.

Este es el modelo de GLCD elegido para las pruebas.

Page 65: 78888821-Curso-Ccs-en-PDF.pdf

IMPORTANTE: Si tu display no es exactamente este modelo, consulta su hoja de datos para asegurarte que función cumple cada uno de sus pines.

Restricciones legales de CCS

Como decíamos en la introducción, dentro de las librerías incluidas en CCS se ha incluido una especie de licencia. Concretamente, en el código fuente de dichas librerias puede leerse algo como lo siguiente:

///////////////////////////////////////////////////////////////////////////// (C) Copyright 1996, 2004 Custom Computer Services //////// This source code may only be used by licensed users of the CCS //////// C compiler. This source code may only be distributed to other //////// licensed users of the CCS C compiler. No other use, //////// reproduction or distribution is permitted without written //////// permission. Derivative programs created using this software //////// in object code form are not restricted in any way. /////////////////////////////////////////////////////////////////////////////

Esto, traducido al español significa más o menos lo siguiente:

"Este código fuente sólo puede ser utilizado por usuarios con licencia del compilador C CCS. Este código fuente sólo puede ser distribuido a otros usuarios con licencia del compilador C CCS. No se permite ningún otro uso, reproducción o distribución sin el consentimiento escrito. Los programas derivados, creados utilizando este software, pueden distribuirse como código objeto sin limitantes."

Descargas

Puedes descargar la libreria GLCD_K0108.C haciendo click aquí.

Page 66: 78888821-Curso-Ccs-en-PDF.pdf

CCS - Poniendo un poco de orden.

Mantener ordenadas aquellas librerías que compartimos entre distintos proyectos, puede ser una tarea a veces complicada, sobre todo cuando desconocemos métodos eficaces para realizar este trabajo. Este problema existe desde hace muchos años y los desarrolladores de compiladores para C, fueron incluyendo mecanismos eficientes para dar solución a este problema. Las siguientes líneas nos ayudarán a sacar provecho de esas simples, pero poderosas herramientas.

El uso de las librerías es fundamental para el desarrollo de proyectos en “C”.

Sin embargo, cuando tenemos varios proyectos que comparten las mismas

librerías, una gestión deficiente, puede llevarnos al caos.

¿Donde reside el problema?

En aquellas librerías que necesitamos reutilizar, y que por su naturaleza, tenemos que modificar para adaptarlas a nuestros proyectos. Un claro ejemplo de este tipo de librerías es la Flex_LCD.c desarrollada por CCS, que nos permite utilizar los muy comunes LCD’s de 2 líneas.

Habitualmente, esta librería debe ser modificada para adaptarla a nuestras necesidades en cada proyecto. Esta situación se presenta cuando nuestros proyectos requieren el uso de distintos microcontroladores o cuando necesitamos determinados módulos del microcontrolador, cuyos pines de E/S, han sido asignados al LCD dentro de Flex_LCD.c. De aquí en adelante, utilizaremos la librería “Flex_LCD.c” como modelo para el resto del artículo, pero todo lo expuesto es aplicable cualquier librería.

¿Como se modifican estas librerías para su uso?

Aquí es donde surge el caos entre los distintos proyectos que tenemos entre manos o que hemos realizado. Analicemos las tres alternativas, de uso más frecuente:

La forma usual e ineficaz.

Tenemos una única librería ubicada en el directorio de librerías (library), y cuando nos hace falta, la modificamos. Esta suele ser una práctica muy habitual. Cada vez que empezamos un nuevo proyecto modificamos la librería y la adaptamos a la necesidad del momento.

Pero: ¿qué ocurre cuando debemos modificar y recompilar un proyecto hecho con anterioridad? Si los pines utilizados en el proyecto anterior y el actual coinciden, no tendremos problema alguno. Sin embargo, es frecuente que no coincidan los pines asignados al LCD del antiguo proyecto con los del actual.

Page 67: 78888821-Curso-Ccs-en-PDF.pdf

Por lo que si compilamos un proyecto antiguo, es muy probable que no funcione correctamente.

La solución común al problema anterior, es tener anotado en algún lugar la asignación de pines para cada proyecto y modificar la librería antes de compilar cada uno. Como se pude ver, es un proceso tedioso que exige un alto grado de orden para mantener la funcionalidad de nuestros proyectos.

El método de la copia

Una alternativa que puede solucionar el problema anterior, es tener una copia de la librería en el directorio de cada proyecto. Luego modificamos la copia, para ajustarla a la configuración según sea el caso. Esto permite que podamos compilar cada proyecto una y otra vez, sin necesidad de modificar la librería, ya que cada proyecto tiene una copia adaptada según sus necesidades.

Es una solución también bastante habitual, pero no idónea; ¿qué ocurre si necesitamos modificar la librería porque tenemos una nueva versión de la misma? Tendremos que ir buscando por el laberinto de directorios de proyectos cada copia de la librería vieja y sustituirla por la nueva.

Se puede argumentar que hoy en día con la velocidad de proceso y las herramientas de búsqueda de las PC, este trabajo no será en extremo tedioso. Pero aunque lográsemos encontrar y sustituir todas las copias en un corto espacio de tiempo, tendremos otro problema añadido, y es que cada copia de la librería está “personalizada” para su proyecto. La situación anterior nos obliga a reconfigurar la nueva versión de la copia, de acuerdo a la configuración de cada proyecto, trabajo que hicimos la primera vez que copiamos la librería hacia el directorio del proyecto.

Utilizando las directivas del pre-procesador

Esta es la forma correcta y eficaz de hacerlo. Este método es el que adoptaremos y nos permitirá manejar las librerías sin sufrir dolores de cabeza. Consiste en definir la asignación de pines, en algún lugar fuera de la librería, bien en fichero aparte, o bien en el programa principal del proyecto. ¿Cómo podemos modificar la asignación de pines fuera de la librería? La forma de hacerlo es utilizando las directivas del pre-procesador.

Las directivas del pre-procesador son un conjunto de instrucciones que se utilizan para indicarle al compilador, que debe hacer, ante determinadas situaciones. Aunque generalmente muchos programadores desconocen su utilidad con profundidad, estas directivas son una herramienta muy poderosa para crear variables, reservar memoria, definir constantes, utilizar macros e incluso indicarle al compilador que secciones de código debe compilar y enlazar. En nuestro caso, utilizaremos las directivas del pre-procesador #ifndef <identifier> … #endif.

Page 68: 78888821-Curso-Ccs-en-PDF.pdf

Cuando el pre-procesador se topa con la directiva #ifndef, comprueba si ya existe el identificador <identifier>, si éste no existiese, entonces crea uno con ese nombre, lo agrega a su lista de identificadores y procesa el código ubicado entre #ifndef y #endif, en caso que el identificador [[<identifier>]] exista, se ignora todo el código ubicado en el cuerpo de la llamada a la directiva.

La técnica descrita anteriormente es precisamente la que vamos a utilizar para gestionar de manera eficiente, el uso de nuestras librerías. Al revisar la sección de Flex_LCD, donde se asignan los pines al microcontrolador, nos topamos con el siguiente código:

#define LCD_DB4 PIN_B4#define LCD_DB5 PIN_B5#define LCD_DB6 PIN_B6#define LCD_DB7 PIN_B7#define LCD_RS PIN_C0#define LCD_RW PIN_C1#define LCD_E PIN_C2

Ahora simplemente metemos esta sección de código en el cuerpo de una llamada a #ifndef con nombre de identificador _FLEX_LCD, el código resultante quedará de la siguiente forma:

#ifndef _FLEX_LCD #define _FLEX_LCD #define LCD_DB4 PIN_B4 #define LCD_DB5 PIN_B5 #define LCD_DB6 PIN_B6 #define LCD_DB7 PIN_B7 #define LCD_RS PIN_C0 #define LCD_RW PIN_C1 #define LCD_E PIN_C2#endif

Si no definimos nada en el programa principal o en su fichero de cabecera, el pre-procesador asignará a la LCD los pines según el código de la librería Flex_LCD. Si queremos modificar la asignación de pines para nuestro proyecto, escribiremos en el fichero principal de nuestro proyecto, o en su fichero de cabecera, el siguiente fragmento de código:

#define _FLEX_LCD#define LCD_DB4 PIN_C4#define LCD_DB5 PIN_C5#define LCD_DB6 PIN_C6#define LCD_DB7 PIN_C7#define LCD_RS PIN_A0#define LCD_RW PIN_A1#define LCD_E PIN_A2#include Flex_LCD.c

Page 69: 78888821-Curso-Ccs-en-PDF.pdf

Esto hace que se asignen los pines del microcontrolador a la LCD tal y como se especifica en nuestro programa principal y que la definición de la librería sea ignorada. Como puede verse, la librería ha sufrido un pequeño cambio que nos ayudará a mantener gestionado su uso y nos facilitará la vida a partir de este momento. Es muy importante que esta asignación se haga antes de incluir la librería (#include Flex_LCD.c), ya que de no hacerlo así, el pre-procesador asignará los pines según la definición que se hace dentro de la librería y se producirá un conflicto con la definición realizada en el programa principal.

Con este método, solo tendremos una librería para todos nuestros proyectos y la personalización se realizará dentro de cada proyecto; sin que por ello tengamos que hacer copias o modificar el fichero original. Además, la librería estará perfectamente localizable dentro de su directorio, por lo que si obtuviésemos una nueva versión, bastará con actualizar y modificar una sola copia.

Otra razón para utilizar esta forma de proceder, es la posibilidad de reconocer la dependencia entre los distintos archivos de nuestros proyectos o entre distintas librerías. Por ejemplo, si creamos una librería que utilice el display como salida, podremos escribir en el código de nuestra librería:

#ifndef _FLEX_LCD #error Es necesario incluir la librería Flex_LCD#endif

De esta forma enviamos un mensaje de error para avisar que es preciso incluir una o varias librerías.

programas de ejemplo