Está en la página 1de 69

CCS - Comentarios

Los comentarios son tiles para informar al que lee nuestro cdigo (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 funcin o del nombre de una variable. Los comentarios pueden ocupar ms de una lnea de largo. Pueden utilizarse para deshabilitar momentneamente un trozo de cdigo. 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 lnea.

CCS - Variables
La programacin seria prcticamente 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.

En esta analoga, 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 bsico de datos, con cuatro modificadores posibles. Podemos utilizar variables de cualquiera de esos tipos. La tabla siguiente muestra los tipos disponibles:
Tipo short short int int char unsigned unsigned int signed signed int long long int signed long float 1 1 8 8 8 8 8 8 16 16 16 32 Ancho (Bits) 0o1 0o1 0 a 255 0 a 255 0 a 255 0 a 255 -128 a 127 -128 a 127 0 a 65536 0 a 65536 -32768 a 32767 3.4E-38 a 3.4E+38 Rango

Si miras con atencin 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.

Declaracin Las variables deben ser declaradas antes de ser utilizadas en el programa. El proceso de declaracin de variables le dice a CCS de que tipo son y como se llaman. Al igual las dems 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 lnea permite a nuestro programa emplear la variable temperatura, que ser capaz de albergar cualquier valor comprendido entre 0 y 255. Asignacin 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).

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 funcin, ser "visible" solo dentro de sta:

funcion1 () { char letra; . . . .} En el ejemplo anterior, la variable tipo char llamada letra solo podr utilizarse dentro de la funcin funcion1 (). Si intentamos utilizarla fuera de ella, el compilador nos dar un error. Si declaramos una variable fuera de cualquier funcin, 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 expresin. 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 automticamente a int cualquier expresin que contenga variables char, short o int. Esta conversin solo tiene efecto mientras se realizan los clculos. Las variables en s mismas no cambian su tipo.

Las reglas de conversin de tipos hacen que el resultado de una operacin sea siempre el mismo que el de la variable ms 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 podra 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 slo contendr los 8 bits menos significativos de ese resultado, es decir, el decimal 196. Si hubisemos 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 ingls "preprocessor directives", son comandos que interpreta el primer paso de la compilacin que lleva a cabo CCS. Las directivas ms comunes son #define e #include, pero deberas dar un vistazo a todas.

#ASM / #ENDASM

Este par de instrucciones permite que utilicemos un bloque de instrucciones en assembler dentro de nuestro cdigo 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 cdigo ASM a CCS. Si en lugar de #ASM utilizamos #ASM ASIS, CCS no intentar efectuar cambios de bancos de memoria automticos para las variables que no pueden ser accedidas desde el banco actual. El cdigo assembler es utilizado "as-is" ("como es"). #BIT Permite crear una nueva variable de un bit de tamao, que es colocada en la memoria del PIC en la posicin del byte x y el bit y. Esto es 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 short. El formato de #BIT es el siguiente: #BIT nombre = x.y Donde nombre es un nombre de variable CCS vlido, x es una constante o una variable CCS vlida 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 interrupcin 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 tamao, que es colocada en la memoria del PIC en la posicin 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 vlido, y x es una constante o una variable CCS vlida. Estos son algunos ejemplos de uso: #BYTE STATUS = 3 #BYTE PORTB = 6

#DEFINE La instruccin #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 cdigo ASM, si no que el preprocesador realiza los reemplazos que ellas indican en el momento de la compilacin. El uso de #DEFINE permite construir programas ms ordenados y fciles 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 difciles de comprender.

#DEFINE es una potente herramienta para la creacin 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 cdigo anterior, hace lo mismo que si hubisemos 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 cdigo apropiado para la cantidad de RAM, ROM y juego de instrucciones disponibles. Para los chips con ms 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 aadir *=16 a continuacin 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 ms opciones que podemos agregar en las lneas #DEVICE:

ADC=x : Determina el nmero de [bit]]s que devuelve la funcin read_adc().

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

ICD=TRUE : Genera cdigo compatible con el ICD de [www.microchip.com Microchips]].

#DEVICE PIC16F877 ICD=TRUE//PIC 1616F877, punteros de 8 bits y cdigo 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 extensin .h correspondiente cuales son los valores posibles para ese microcontrolador. Estn al comienzo del archivo, en forma de comentarios. Algunos valores comunes son

Tipo de oscilador: LP, XT, HS, RC Wach Dog Timer: WDT, NOWDT Proteccin de cdigo: PROTECT, NOPROTECT Power Up Timer: PUT, NOPUT Brown Out Reset: BROWNOUT, NOBROWNOUT #INCLUDE

Permite incluir en nuestro programa uno o mas archivos (conocidos como header file) que posean extensin .h. Estos archivos contienen informacin 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 montn de cosas cada vez que comenzamos un programa nuevo: basta con incluir el .h correspondiente. La forma de utilizar esta instruccin 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 funcin que le sigue (en el cdigo fuente CCS) es una funcin de interrupcin. Estas funciones no deben tener parmetros. Por supuesto, no todos los PICs soportan todas las directivas disponibles: 1. INT_AD Conversin A/D finalizada. 2. I NT_ADOF Conversin A/D timeout. 3. INT_BUSCOL Colisin en bus. 4. INT_BUTTON Pushbutton. 5. INT_CCP1 Unidad CCP1.

6. INT_CCP2 Unidad CCP2. 7. INT_COMP Comparador. 8. INT_EEPROM Escritura finalizada. 9. INT_EXT Interrupcin externa. 10. INT_EXT1 Interrupcin externa #1. 11. INT_EXT2 Interrupcin externa #2. 12. INT_I2C Interrupcin 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 transmisin RS-232 vaco. 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 () { ... }

CCS - Operadores
En CCS los operadores cumplen un rol importante. Quizs C sea uno de los lenguajes que ms operadores tiene. Una expresin es una combinacin de operadores y operandos. En la mayora de los casos, los operadores de CCS siguen las mismas reglas que en lgebra, y se llaman de la misma manera.

Operadores aritmticos CCS posee cinco operadores aritmticos:


+ (suma) - (substraccin) * (multiplicacin) / (divisin) % (mdulo)

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 % (mdulo) solo puede emplearse con enteros. Devuelve el resto de una divisin 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 tambin provee atajos para utilizar los operadores aritmticos. Hay algunas operaciones que se repiten a menudo cuando creamos nuestros programas, y estos atajos ayudan a que podamos escribir nuestro cdigo ms rpidamente. 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 a %= b es lo mismo que a = a * b Operadores Relacionales

Los operadores relacionales comparan dos valores, y devuelven un valor lgico basado en el resultado de la comparacin. 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 comparacin, sera siempre 0 o 1. 0 significa que el resultado de la comparacin ha sido falso, y 1 que ha sido verdadero. Operadores Lgicos Los operadores lgicos disponibles permiten realizar las operaciones AND, OR y NOT:

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

Por supuesto, puede emplearse ms de un operador lgico en la misma expresin: 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)

<< (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 = 11111000 b = 00001101 luego 01111000 AND 00001101 = 00001000 01111000 OR 00001101 = 01111101 01111000 XOR 00001101 = 01110101 NOT 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 tambin 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 cdigo ms rpidamente. 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

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

Quedan por ver aun dos operadores ms:


++ Operador incremento -- Operador decremento

Estos operadores permiten sumar (o restar) uno al valor de una variable. Lo que generalmente haramos 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 fcil de utilizar en expresiones complejas. Precedencia de los operadores Al igual que ocurre en lgebra, en CCS los operadores se evalan en un orden determinado. La siguiente lista muestra este orden, ordenado de mayor a menor:

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

CCS - Punteros
Una de las caractersticas ms 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 artculo fue escrito por Pedro (PalitroqueZ), un amigo de uControl. Su direccin de correo electrnico es palitroquez@gmail.com. Qu es un puntero? Un puntero es una variable cuya finalidad es almacenar nmeros ENTEROS POSITIVOS. Estos nmeros no son nmeros 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 muchsimas cosas:

Acceso a la memoria RAM del PIC. Ahorrar memoria RAM. Modificar ms de una variable dentro de una funcin (y por consiguiente devolver mas de un valor)

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

Permite crear tablas con montones de datos (en los PIC que soporten acceso a la memoria de programa).

En un ordenador se ampla el abanico de opciones.

Ms abajo veremos detalladamente como hacer todo esto.

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 tamao varia de acuerdo al tipo de dato. Como todo en el mundo electrnico/digital, est basado en 2 cosas:

El registro: es la casilla donde se almacena el dato. La direccin del registro: es la posicin en la memoria donde est alojado el registro.

as pues tenemos 2 elementos diferentes pero que se relacionan. Conociendo la direccin del registro o variable y pudindolo manejar nos da un poderosa herramienta para agilizar/simplificar nuestros programas. Como podemos acceder a la direccin de una variable? En CCS se hace a travs 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:

Cuando detenemos en delay_cycles(1) vemos que en k se guarda la direccin de la variable t, y que guarda t? guarda el nmero 5. todo se realiza usando memoria RAM el registro de propsito general GPR. Vamos a cambiar ligeramente el cdigo. 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?

Para responder esta pregunta vamos a cambiar el cdigo 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 simulacin:

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 direccin que ocupa un byte [0xA] en u tenemos una direccin que ocupa 2 byte [0xB - 0xC] en v tenemos una direccin que ocupa 4 bytes [0xD - 0x10]

Probando punteros, primera parte Siempre que se declare una variable puntero, al momento de usarlo se debe especificar la direccin 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 nmero por valor de la direccin 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; int *p; k=0xfa; *p=0x5; delay_cycles (1); } Dentro del cdigo reconocemos de inmediato quien es el puntero: el que tiene el smbolo * debe ir antes de la letra p y sin separacin: *p as es como se debe declarar. si nos vamos a MPLAB-SIM, y trazamos hasta delay_cycles(1) vemos en la ventana LOCAL: // variable normal // la variable puntero // k <- 0xfa

pero... no aparece nada en p! Por que? Es simple: porque no fijamos una direccin que apuntara p, y esto es muy importante saberlo, era lo que se deca al inicio de este artculo. Vamos a darle la direccin de k: #include <18F4550.h> #use delay(clock=4000000) //******************************* void main(){ int k; int *p; p=&k; k=0xfa; *p=0x5; // variable normal // la variable puntero // direccin de k copiada a p // k <- 0xfa // k <- 0x5

delay_cycles(1); } el resultado:

Ahora si funciona correctamente el cdigo. Si ven la lnea: p=&k; // direccin de k copiada a p

Podran observar que se usa el puntero sin el *. Esto significa que se guardar all una direccin y el compilador lo interpreta de esa manera.

Y con esta lnea: *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 nmeros enteros positivos, en realidad esto ya es a nivel interno del compilador. Un ejemplo ms: En este cdigo hay 2 punteros y 3 variables normales: #include <18F4550.h> #use delay(clock=4000000) //******************************* void main(){ int i; int *p; int j; int *q; int k; // p=&i; q=&j; // i=0xfa; // i <- 0xfa // direccin de i copiada a p // variable normal // la variable puntero

j=0x11; k=0x22; // *p=0x5; *q=0x33; delay_cycles(1); } // i <- 0x5

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 cdigo para que i sea del tipo float: #include <18F4550.h> #use delay(clock=4000000) //******************************* void main(){ float i; float *p; long j; long *q; int k; // i=2.51; j=0x11; k=0x22; // // i <- 0xfa // variable normal // la variable puntero //

p=&i; q=&j; // *p=3.99; *q=0x33;

// direccin de i copiada a p

// i <- 0x5

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 declaracin de los punteros, se mantienen en 2 bytes, eso quiere decir que para el compilador el tamao 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; int *p; long j; long *q; // variable normal // la variable puntero

int k; // i=2.51; j=0x11; k=0x22; // p=&i; q=&j; // *p=3.99; *q=0x33; delay_cycles(1); } // i <- 0x5 // direccin de i copiada a p // i <- 0xfa

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 direccin en vez de 4 bytes que son los que se necesitan y por eso ocurre ese truncamiento y da ese valor extrao. 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

float *p; long j; long *q; int k; // i=2.51; j=0x11; k=0x22; // p=&i; q=&j; // *p=3.99; *q=0x33;

// la variable puntero

// i <- 0xfa

// direccin de i copiada a p

// i <- 0x5

delay_cycles(1); }

Aqu se lee que est correcto el resultado.

Nota: los punteros tiene un mximo de 2 bytes para almacenar direcciones y el CCS sigue la misma normativa. Hay una directiva llamada #device xxxxxxx

con 4 modos de seleccin: CCS2,CCS3,CCS4 y ANSI. Con CCS2 y CCS3 el tamao (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 tamao 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 direccin de la RAM (dentro de ciertos limites). Algo as como si en ensamblador pusiramos: variable_normal EQU 0xNNNN Esto nos servir porque sera como manipular el contenido de un puntero pero en tiempo de diseo

#include <18F4550.h> #use delay(clock=4000000) //********************************* int dato=0xaa; #locate dato = 0xff // le decimos al compilador que dato estar en la direccin 0xFF //del rea de registro de propsito 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 } // declaramos dato (GPR) y lo cargamos con 0xAA

Fjense 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 direccin 0xAF

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 (Pg. 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 ...

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 deberan leer como puros 0x0 a pesar que compil bien, (similarmente en programas de computadoras pueden ocurrir los lazos infinitos popularmente llamado se colg la mquina) Punteros en funciones Todo lo que hagamos en CCS se hace a travs de funciones o procedimientos, desde el punto de vista matemtico una funcin se define as: Una funcin es una relacin entre dos variables numricas, 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 adems, para que una relacin sea funcin, a cada valor de la variable independiente le corresponde uno o ningn valor de la variable dependiente, no le pueden corresponder dos o ms valores. Aplicndolo a la programacin, significa que podemos tener varios argumentos o parmetros de entrada, pero solo tendremos un dato de salida. Y eso no es todo, en C una funcin pasa los argumentos por valor. que quiere decir esto?

Que cuando llamemos a la funcin y le pasemos el dato como argumento, sta copiar ese dato en su propia funcin 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?

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 hara eso? Simple: se hara pasando el argumento a la funcin como referencia, haciendo referencia a la direccin, es decir lo que se pasar a la funcin es la direccin 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 sabrn los arrays son arreglos de datos que van en direcciones consecutivas, es decir, uno detrs del otro. Un ejemplo de ello: char cadena[7]={'T','o','d','o','P','i','c'}; Si lo probamos en un cdigo: #include <18F4550.h> #use delay(clock=4000000)

//********************************* 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); }

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

Declarando un puntero como char:

char c, *p; lo inicializamos (le damos la direccin 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 segn la precedencia del operador primero est el puntero y luego viene la suma, y as estaramos sumando direcciones que varan, la solucin es usar *(p+i) Y que es eso de que varan? Pues que cuando se recorre el arrays con el puntero, este debe ir sumando direcciones, pero direcciones de nmeros constantes, es decir, si el tipo de datos es 1 byte, entonces el puntero debe acumular nmeros 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 direccin) y el truco est en desplazar al puntero tantas posiciones sea el size del tipo de dato. Este sera el segundo error y la solucin es la misma: *(p+i) Vamos a cambiar ese ejemplo por nmeros long para que se entienda

#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); }

Fjense que p queda inmutable, y lo que hace el programa es contenido[0] + t. Grave error! Arreglando el programa con *(p+t)

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 direccin 0x5 0x5 + 1x2 0x5 + 2x2 0x5 + 3x2 ... " " " -> dame el contenido de la direccin 0x7 -> dame el contenido de la direccin 0x9 -> dame el contenido de la direccin 0xA

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 cdigo del inicio del artculo? 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 fcil la programacin el compilador lo acepta de esta manera. Si p es un puntero -> p = cadena (para el primer ndice del arreglo) es totalmente vlido, se acepta que cadena es un puntero constante, tambin se podra 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 cdigo original, el que tena 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); }

CCS - Funciones
Las funciones son los bloques bsicos con los que construimos un programa en CCS. Adems de la funcin main() que veremos enseguida, un programa CCS tendr seguramente varias funciones ms, 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 funcin. 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 instruccin de CCS, la lnea debe termina con ; (punto y coma). El siguiente ejemplo declara la funcin ejemplo() que devuelve como resultado un valor del tipo long: long ejemplo(); Parmetros Adems de determinar el tipo de resultado que devolver la funcin, en el prototipo podemos especificar que parmetros recibir, y de que tipo sern. 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.

Supongamos que queremos crear una funcin 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. Deberamos escribir as su prototipo: double suma(int a, int b); donde a y b son los valores a sumar. El llamado a la funcin 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 funcin el valor que esta debe devolver es mediante la instruccin return. Vemoslo con el ejemplo de la funcin suma vista mas arriba. La funcin podra 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 funcin es la siguiente: double suma(int a, int b){ return (double) a * b; } Void void significa que la funcin no devolver ningn parmetro. Supongamos que la funcin ejemplo() no debe regresar ningn valor luego de ser llamada. Su prototipo debera ser como sigue: void ejemplo(); Adems, podemos usar void para indicar que la funcin no recibe parmetros: void ejemplo2(void);

en el ejemplo, la funcin ejemplo2() no recibe parmetros, ni devuelve ningn valor. La funcin main() Como hemos visto, el lenguaje C permite la utilizacin de funciones. Pero hay una funcin especial, llamada main () que obligatoriamente debe estar presente, y es el punto de entrada a todo programa en C que escribamos. La funcin main () tiene la siguiente forma: main() { instruccion; instruccion; . . instruccion; } donde instruccion; puede ser cualquier instruccin vlida del CCS o una llamada a otra funcin.

CCS - Uso de LCDs alfanumricos.


En CCS no disponemos de instrucciones especficas 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 mayora de los mdulos LCD estn construidos en base al controlador Hitachi HD44780. LCD.C Para ponernos las cosas ms fciles, 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 modificacin menor). Para incluirlo en nuestro programa, basta con hacer lo siguiente: #INCLUDE "lcd.c" en cuanto al hardware,las lneas de datos deben conectarse de la siguiente manera:

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

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

Como puede verse, se trata de una comunicacin con solo 4 bits de datos. Ms adelante veremos como modificar este archivo para que se pueda emplear con el LCD en otro puerto y/o con otra asignacin 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

// // 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 out struct 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;

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;

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 funcin es la encargada de enviar los comandos de inicializacin necesarios al LCD. Es obligatorio ejecutar esta funcin antes de utilizar el display para escribir sobre l. No recibe ni devuelve valores de ningn tipo. Su forma de uso es tan simple como: lcd_init (); y listo. lcd_putc() Esta seguramente ser la funcin que mas emplearemos. Es la que se encarga de escribir nuestro mensaje en la pantalla. No devuelve valores, pero si

(obviamente) los recibe. La forma de uso es muy simple. Basta con llamarla, pansandole como parmetro una variable o constante tipo char, y la funcin 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. Adems, 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 lnea. \b -> Retrocede una posicin.

Esto quiere decir que si modificamos nuestro cdigo 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:

lcd_gotoxy(x,y) Esta es la funcin que nos permite colocar el cursor en la parte que deseemos de la pantalla. Recibe dos parmetros, ambos de tipo byte. El primero de ellos indica la columna en la que aparecer el primer carcter del texto, y el segundo se refiere a la fila en que lo har. El siguiente cdigo 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 funcin recibe como parmetros la columna' y la fila (ambos de tipo byte) de la que deseamos conocer el contenido, y nos devuelve un char con el contenido. Su uso no podra ser ms 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 mayora de los casos la conexin entre el microcontrolador y el display LCD no coincidir con la especificada en el archivo LCD.C provisto por CCS. Pero eso no quiere decir que debamos redisear nuestro proyecto, ni que sea imposible modificar la configuracin de LCD.C. como puede verse, dentro del cdigo de LCD.C se define una estructura de datos que es la encargada de contener la distribucin de los pines a utilizar, junto a la funcin que desempear. Es el siguiente trozo de cdigo: struct lcd_pin_map { BOOLEAN enable; BOOLEAN rs; BOOLEAN rw; // This structure is overlayed // on to an I/O port to gain // access to the LCD pins. // The bits are allocated from

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 debera 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, habrs notado que en lugar del puerto D estamos usando el puerto B, as que hay que quitar el comentario a la lnea // #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 lnea RW del LCD, debemos quitar las dos tres lneas de cdigo en la que se hace referencia a ella. El listado siguiente corresponde al archivo LCD.C con todas las modificaciones mencionadas, ms 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. Tambin hemos quitado el cdigo de la funcin 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

// 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 // Direccin de la estructura "lcd". #byte lcd = 6 // Direccin 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 lneas #define lcd_line_two 0x40 // Direccin de la LCD RAM para la 2da. lnea // //Defino la cadena de inicializacin 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;

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); }

// 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 grficos para GLCD K0108 en CCS


El compilador CCS proporciona una librera capaz de dibujar primitivas sobre varios modelos de displays LCD grficos o GLCD (por Graphic Liquid Cristal Display). Hay versiones de esta librera 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 tambin sea un usuario registrado de CCS. Esto limita mucho su uso con fines educativos. De hecho, si quisisemos exponer aqu un programa que grafique algo en un GLCD, estaramos 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 librera propia, que usaremos de ahora en ms para nuestros proyectos. La librera 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 lneas 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 simtricas, evitando utilizar funciones como seno, coseno o potencias, que volveran muy lenta la tarea del trazado.

GLCD_limpiar (color)

Esta es la funcin que "pinta" toda la pantalla con uno u otro color. Si recibe como parmetro 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 tambin es muy sencillo, y se "apoya" en GLCD_envia BYTE () para escribir en el GLCD. Recorre ambas mitades del GLCD, pgina por pgina, de arriba hacia abajo, escribiendo "0x00" o "0xFF" segn se haya elegido pintar o borrar.

(Hz clic sobre las imgenes para ampliarlas)

En la primer imgen, 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 funcin para limpiar la pantalla. Puedes ver un ejemplo de uso de esta funcin aqu.

GLCD_inicializa (modo) Esta es la primera funcin de la librera que debe llamar nuestro programa. Se encarga de inicializar el GLCD, y el parmetro "modo" determina si estar encendido (si recibe un "1") o apagado (si recibe un "0").

Puedes ver un ejemplo de uso de esta funcin aqu. GLCD_punto(x, y, color) Esta es la "primitiva grfica" 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 vlidos de 0 a 127 (izquierda a derecha). y: un byte, es la coordenada "y" (vertical), con valores vlidos de 0 a 63 (arriba a abajo) color: un bit, "0" = apagado, "1" = encendido.

(Hz clic sobre la imgen para ampliarla)

Puedes ver un ejemplo de uso de esta funcin aqu. GLCD_linea(x1, y1, x2, y2, color) La lnea tambin resulta indispensable a la hora de dibujar un grfico. Los parametros que recibe GLCD_linea(x1, y1, x2, y2, color) son:

x1: un byte, es la coordenada "x" (horizontal) del primer extremo de lnea, con valores vlidos de 0 a 127 (izquierda a derecha). y1: un byte, es la coordenada "y" (vertical) del primer extremo de lnea, con valores vlidos de 0 a 63 (arriba a abajo). x2: un byte, es la coordenada "x" (horizontal) del segundo extremo de lnea, con valores vlidos de 0 a 127 (izquierda a derecha). y2: un byte, es la coordenada "y" (vertical) del segundo extremo de lnea, con valores vlidos de 0 a 63 (arriba a abajo). color: un bit, "0" = lnea en blanco, "1" = lnea en negro.

la la la la

(Hz clic sobre las imgenes para ampliarlas)

Puedes ver un ejemplo de uso de esta funcin aqu. GLCD_rectangulo(x1, y1, x2, y2, color) Los rectngulos de dibujan (internamente) mediante cuatro llamadas a la funcin GLCD_linea. Los parametros que recibe GLCD_rectangulo(x1, y1, x2, y2, color) son:

x1: un byte, es la coordenada "x" (horizontal) de la esquina superior izquierda del rectngulo, con valores vlidos de 0 a 127 (izquierda a derecha). y1: un byte, es la coordenada "y" (vertical) de la esquina superior izquierda del rectngulo, con valores vlidos de 0 a 63 (arriba a abajo). x2: un byte, es la coordenada "x" (horizontal) de la esquina inferior derecha del rectngulo, con valores vlidos de 0 a 127 (izquierda a derecha). y2: un byte, es la coordenada "y" (vertical) de la esquina inferior derecha del rectngulo, con valores vlidos de 0 a 63 (arriba a abajo). color: un bit, "0" = rectngulo en blanco, "1" = rectngulo en negro.

(Hz clic sobre las imgenes para ampliarlas)

Puedes ver un ejemplo de uso de esta funcin aqu. GLCD_caja(x1, y1, x2, y2, color) Las "cajas" son rectngulos pintados en su interior con el mismo color que el borde exterior. Tambin se dibujan (internamente) mediante llamadas a la funcin GLCD_linea.

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 rectngulo, con valores vlidos de 0 a 127 (izquierda a derecha). y1: un byte, es la coordenada "y" (vertical) de la esquina superior izquierda del rectngulo, con valores vlidos de 0 a 63 (arriba a abajo). x2: un byte, es la coordenada "x" (horizontal) de la esquina inferior derecha del rectngulo, con valores vlidos de 0 a 127 (izquierda a derecha). y2: un byte, es la coordenada "y" (vertical) de la esquina inferior derecha del rectngulo, con valores vlidos de 0 a 63 (arriba a abajo). color: un bit, "0" = caja en blanco, "1" = caja en negro.

(Hz clic sobre las imgenes para ampliarlas)

Puedes ver un ejemplo de uso de esta funcin aqu. GLCD_circulo(x1, y1, radio, color) Esta es la funcin que dibuja un circulo. El interior del circulo permanece del color del fono. Estrictamente hablando, se dibuja solo la circunferencia.

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 vlidos de 0 a 127 (izquierda a derecha). y1: un byte, es la coordenada "y" (vertical) del centro del circulo, con valores vlidos 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.

(Hz clic sobre las imgenes para ampliarlas)

Puedes ver un ejemplo de uso de esta funcin 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

#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, debers 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 lneas de control (ver el pinout del GLCD en la seccin 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

Veamos ahora las funciones internas bsicas de la librera:

GLCD_enviaBYTE (lado, dato) Esta funcin enva un byte a uno u otro lado del display. Como mencionamos antes, debemos seleccionar previamente, mediante la activacin de CS1 o CS2, cual utilizaremos. El parmetro (tipo int1) "lado" es el que define a que mitad del GLCD ir a parar el "dato". El resto de la funcin no tiene ningn 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 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 funcin que permite leer un byte desde el GLCD. Igual que la anterior, recibe como parmetro la informacin 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 funcin devuelve un BYTE con el dato ledo. GLCD_letra (carcter, color, fuente) El presente artculo esta incompleto.

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 librera ofrece soporte para los GLCD de 128x64 pxeles, monocromticos, 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.

IMPORTANTE: Si tu display no es exactamente este modelo, consulta su hoja de datos para asegurarte que funcin cumple cada uno de sus pines. Restricciones legales de CCS Como decamos en la introduccin, dentro de las libreras incluidas en CCS se ha incluido una especie de licencia. Concretamente, en el cdigo 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 espaol significa ms o menos lo siguiente: "Este cdigo fuente slo puede ser utilizado por usuarios con licencia del compilador C CCS. Este cdigo fuente slo puede ser distribuido a otros usuarios con licencia del compilador C CCS. No se permite ningn otro uso, reproduccin o distribucin sin el consentimiento escrito. Los programas derivados, creados utilizando este software, pueden distribuirse como cdigo objeto sin limitantes." Descargas Puedes descargar la libreria GLCD_K0108.C haciendo click aqu.

CCS - Poniendo un poco de orden.


Mantener ordenadas aquellas libreras que compartimos entre distintos proyectos, puede ser una tarea a veces complicada, sobre todo cuando desconocemos mtodos eficaces para realizar este trabajo. Este problema existe desde hace muchos aos y los desarrolladores de compiladores para C, fueron incluyendo mecanismos eficientes para dar solucin a este problema. Las siguientes lneas nos ayudarn a sacar provecho de esas simples, pero poderosas herramientas. El uso de las libreras es fundamental para el desarrollo de proyectos en C. Sin embargo, cuando tenemos varios proyectos que comparten las mismas libreras, una gestin deficiente, puede llevarnos al caos. Donde reside el problema? En aquellas libreras que necesitamos reutilizar, y que por su naturaleza, tenemos que modificar para adaptarlas a nuestros proyectos. Un claro ejemplo de este tipo de libreras es la Flex_LCD.c desarrollada por CCS, que nos permite utilizar los muy comunes LCDs de 2 lneas. Habitualmente, esta librera debe ser modificada para adaptarla a nuestras necesidades en cada proyecto. Esta situacin se presenta cuando nuestros proyectos requieren el uso de distintos microcontroladores o cuando necesitamos determinados mdulos del microcontrolador, cuyos pines de E/S, han sido asignados al LCD dentro de Flex_LCD.c. De aqu en adelante, utilizaremos la librera Flex_LCD.c como modelo para el resto del artculo, pero todo lo expuesto es aplicable cualquier librera. Como se modifican estas libreras 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 ms frecuente: La forma usual e ineficaz. Tenemos una nica librera ubicada en el directorio de libreras (library), y cuando nos hace falta, la modificamos. Esta suele ser una prctica muy habitual. Cada vez que empezamos un nuevo proyecto modificamos la librera 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.

Por lo que si compilamos un proyecto antiguo, es muy probable que no funcione correctamente. La solucin comn al problema anterior, es tener anotado en algn lugar la asignacin de pines para cada proyecto y modificar la librera 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 mtodo de la copia Una alternativa que puede solucionar el problema anterior, es tener una copia de la librera en el directorio de cada proyecto. Luego modificamos la copia, para ajustarla a la configuracin segn sea el caso. Esto permite que podamos compilar cada proyecto una y otra vez, sin necesidad de modificar la librera, ya que cada proyecto tiene una copia adaptada segn sus necesidades. Es una solucin tambin bastante habitual, pero no idnea; qu ocurre si necesitamos modificar la librera porque tenemos una nueva versin de la misma? Tendremos que ir buscando por el laberinto de directorios de proyectos cada copia de la librera vieja y sustituirla por la nueva. Se puede argumentar que hoy en da con la velocidad de proceso y las herramientas de bsqueda de las PC, este trabajo no ser en extremo tedioso. Pero aunque logrsemos encontrar y sustituir todas las copias en un corto espacio de tiempo, tendremos otro problema aadido, y es que cada copia de la librera est personalizada para su proyecto. La situacin anterior nos obliga a reconfigurar la nueva versin de la copia, de acuerdo a la configuracin de cada proyecto, trabajo que hicimos la primera vez que copiamos la librera hacia el directorio del proyecto. Utilizando las directivas del pre-procesador Esta es la forma correcta y eficaz de hacerlo. Este mtodo es el que adoptaremos y nos permitir manejar las libreras sin sufrir dolores de cabeza. Consiste en definir la asignacin de pines, en algn lugar fuera de la librera, bien en fichero aparte, o bien en el programa principal del proyecto. Cmo podemos modificar la asignacin de pines fuera de la librera? 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 cdigo debe compilar y enlazar. En nuestro caso, utilizaremos las directivas del pre-procesador #ifndef <identifier> #endif.

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 cdigo ubicado entre #ifndef y #endif, en caso que el identificador [[<identifier>]] exista, se ignora todo el cdigo ubicado en el cuerpo de la llamada a la directiva. La tcnica descrita anteriormente es precisamente la que vamos a utilizar para gestionar de manera eficiente, el uso de nuestras libreras. Al revisar la seccin de Flex_LCD, donde se asignan los pines al microcontrolador, nos topamos con el siguiente cdigo: #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 seccin de cdigo en el cuerpo de una llamada a #ifndef con nombre de identificador _FLEX_LCD, el cdigo 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 segn el cdigo de la librera Flex_LCD. Si queremos modificar la asignacin de pines para nuestro proyecto, escribiremos en el fichero principal de nuestro proyecto, o en su fichero de cabecera, el siguiente fragmento de cdigo: #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

Esto hace que se asignen los pines del microcontrolador a la LCD tal y como se especifica en nuestro programa principal y que la definicin de la librera sea ignorada. Como puede verse, la librera ha sufrido un pequeo cambio que nos ayudar a mantener gestionado su uso y nos facilitar la vida a partir de este momento. Es muy importante que esta asignacin se haga antes de incluir la librera (#include Flex_LCD.c), ya que de no hacerlo as, el pre-procesador asignar los pines segn la definicin que se hace dentro de la librera y se producir un conflicto con la definicin realizada en el programa principal. Con este mtodo, solo tendremos una librera para todos nuestros proyectos y la personalizacin se realizar dentro de cada proyecto; sin que por ello tengamos que hacer copias o modificar el fichero original. Adems, la librera estar perfectamente localizable dentro de su directorio, por lo que si obtuvisemos una nueva versin, bastar con actualizar y modificar una sola copia. Otra razn para utilizar esta forma de proceder, es la posibilidad de reconocer la dependencia entre los distintos archivos de nuestros proyectos o entre distintas libreras. Por ejemplo, si creamos una librera que utilice el display como salida, podremos escribir en el cdigo de nuestra librera: #ifndef _FLEX_LCD #error Es necesario incluir la librera Flex_LCD #endif De esta forma enviamos un mensaje de error para avisar que es preciso incluir una o varias libreras.

programas de ejemplo

También podría gustarte