Curso Ccs en PDF
Curso Ccs en PDF
Curso Ccs en PDF
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.
Los comentarios pueden ocupar más de una línea de largo. Pueden utilizarse
para deshabilitar momentáneamente un trozo de código.
/* Esto es un comentario */
Es decir, todo lo que haya escrito entre /* y */ será tomado por el compilador
como un comentario.
// Esto es un comentario
En este caso, el comentario comienza en // y se extiende hasta el final de la
línea.
CCS - Variables
short 1 0o1
int 8 0 a 255
char 8 0 a 255
unsigned 8 0 a 255
long 16 0 a 65536
Declaración
tipo nombre_de_la_variable;
int temperatura;
Asignación de valores
Asignar un valor a una variable es una tarea bien simple. Basta con hacer lo
siguiente:
nombre_de_variable = valor;
count = 100;
int a = 0;
int i = 10;
int j;
j = 1;
Si una variable se declara dentro de una función, 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 función funcion1 (). Si intentamos utilizarla fuera de ella, el
compilador nos dará un error.
char letra;
main() {
.
.
.
.}
funcion1 () {
.
.
.}
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.
(tipo) valor
long c;
c = (long) (a * b);
Las directivas más comunes son #define e #include, pero deberías dar un
vistazo a todas.
#ASM / #ENDASM
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:
#BIT
#BYTE
#BYTE nombre = x
#BYTE STATUS = 3
#BYTE PORTB = 6
#DEFINE
#DEFINE TRUE 1
#DEFINE pi 3.14159265359
Como puedes ver, #DEFINE puede hacer mucho por tus programas.
#DEVICE
WRITE_EEPROM=ASYNC :
HIGH_INTS=TRUE : Define la prioridad de las interrupciones en los
PIC18.
#FUSE
#FUSE opciones
#INCLUDE
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.
#INCLUDE <archivo>
#INCLUDE <PIC16F877A.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:
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. 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
+ (suma)
- (substracción)
* (multiplicación)
/ (división)
% (módulo)
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".
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
a -= b es lo mismo que a = a - b
a %= b es lo mismo que a = a * b
Operadores Relacionales
Operadores Lógicos
a = b && ( q || n )
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)
a&b=8
a | b = 125
a ^ b = 117
~ a = 135
a = 11111000
b = 00001101
luego
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.
Otros operadores
++ Operador incremento
-- Operador decremento
a=a+1
0 así:
a=a-1
a++
o así:
a--
el resultado sera el mismo, pero es mas corto de escribir, y mas fácil de utilizar
en expresiones complejas.
()
signo +, signo -, ++, --, !, (<tipo>)
*, /, %
+, -
<, <=, >, >=
==, !=
&&, ||
=, +=, -=, *=, /=, %=
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.
¿Qué es un puntero?
Para entender el uso de estas variables especiales hay que comprender bien
un concepto:
Ejemplo1:
#include <18F4550.h>
#use delay (clock=4000000)
void main(){
int t,k;
t=5;
k= &t;
delay_cycles (1);
}
#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);
}
#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:
Esto quiere decir que se le debe pasar el número por valor de la dirección de la
variable normal. Recordemos que:
#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);
}
#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:
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.
Y con esta línea:
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:
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:
#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
j=0x11;
k=0x22;
//
*p=0x5; // i <- 0x5
*q=0x33;
delay_cycles(1);
}
#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;
//
p=&i; // dirección de i copiada a p
q=&j; //
*p=3.99; // i <- 0x5
*q=0x33;
delay_cycles(1);
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;
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);
}
¿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.
#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;
//
p=&i; // dirección de i copiada a p
q=&j;
//
*p=3.99; // i <- 0x5
*q=0x33;
delay_cycles(1);
}
#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:
Esto nos servirá porque sería como manipular el contenido de un puntero pero
en tiempo de diseño
#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
}
Fíjense que el puntero p ocupa 2 bytes a pesar que está declarado como int (1
byte).
float dato=1.23456789;
#locate dato = 0x7FB
...
Si que funcionó. Pero, ¿que pasa si asignamos el dato a 0x800?
Punteros en funciones
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.
#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);
}
#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)
//*********************************
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:
char c, *p;
p=&cadena[0];
for (t=0;t<7;t++){
c= *p + t;
}
¿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
Vamos a cambiar ese ejemplo por números 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);
}
Fíjense que p queda inmutable, y lo que hace el programa es contenido[0] + t.
¡Grave error!
*(p+t) =
Ejemplos validos:
#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 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
nombre_de_la_funcion() {
instruccion;
instruccion;
.
.
instruccion; }
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).
long ejemplo();
Parámetros
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 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:
int a, b;
double resultado;
a = 10;
b = 250;
resultado = suma (a, b);
Return
Void
void ejemplo();
Además, podemos usar void para indicar que la función no recibe parámetros:
void ejemplo2(void);
en el ejemplo, la función ejemplo2() no recibe parámetros, ni devuelve ningún
valor.
La función main()
main() {
instruccion;
instruccion;
.
.
instruccion; }
donde instruccion; puede ser cualquier instrucción válida del CCS o una
llamada a otra función.
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).
#INCLUDE "lcd.c"
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
//
//
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
lcd_init()
lcd_init ();
y listo.
lcd_putc()
Lcd_putc ("uControl.com.ar");
Esto quiere decir que si modificamos nuestro código para que quede así:
Lcd_putc ("\f");
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:
lcd_getc(x,y)
char a;
a = lcd_getc(5,2);
Modificando LCD.C
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
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 siguiente
corresponde 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
// 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;
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;
}
}
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.
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
GLCD_limpiar (color)
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.
GLCD_punto(x, y, color)
Los parametros que recibe GLCD_linea(x1, y1, x2, y2, color) son:
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.
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 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.
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.
Los parametros que recibe GLCD_caja(x1, y1, x2, y2, color) son:
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.
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.
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,
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.
Tambien hemos definido un BYTE que guardará el dato que leyamos desde el
GLCD:
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".
GLCD_leeBYTE (lado)
El equipo de uControl.
Por ahora, nuestra librería ofrece soporte para los GLCD de 128x64 píxeles,
monocromáticos, que tienen un controlador Samsung KS0108 (o compatibles).
/////////////////////////////////////////////////////////////////////////
//// (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. ////
/////////////////////////////////////////////////////////////////////////
"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
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:
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.
#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
#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 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.
#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