Compilador C CCS
El compilador C de CCS ha sido desarrollado
específicamente para MCU PIC, obteniendo la
máxima optimización del compilador con estos
dispositivos. Dispone de un amplia librería de
funciones predefinidas, comandos de
preprocesado y ejemplos. Además, suministra
los controladores (drivers) para diversos
dispositivos como LCD, convertidores AD, relojes
de tiempo real, comunicación serie, etc.
El C CCS es C estándar y, además de las
directivas estándar (#include, etc.), suministra
unas directivas específicas para PIC (#device,
etc.); además incluye funciones específicas
(bit_set(), etc.). Se suministra con un editor que
permite controlar la sintaxis del programa.
Para más información:
http://www.ccsinfo.com
Contenido de un programa en C de CCS
*Directivas de preprocesado: Controlan la
conversión del programa a código máquina por parte
del compilador.
*Programas o funciones: Conjunto de instrucciones.
Puede haber uno o varios; en cualquier caso siempre
debe haber uno definido como principal (main())
*Instrucciones: Indican como se debe comportar el
PIC en todo momento.
*Comentarios: Permiten describir lo que significa
cada línea del programa.
Tipos de datos
Variables
Las variables se utilizan para nombrar
posiciones de memoria RAM; se deben declarar,
obligatoriamente, antes de utilizarlas; para ello
se debe indicar el nombre y el tipo de dato que
se manejará. Se definen de la siguiente forma:
TIPO Nombre_variable [=Valor_inicial]
TIPO referencia al tipo de datos
Nombre_variable identificador de la variable
Valor_inicial opcional
Ejemplo
#include <16f877.h>
#USE DELAY (CLOCK=4000000)
Int16 contador; // variable global
void FUNCION(void) {
char K, letra=‘A’; // variables locales
}
void main() {
int8 x; // variable local
}
Funciones
Las funciones son bloques de sentencias
(instrucciones); todas las sentencias se deben enmarcar
dentro de las funciones. Al igual que las variables, las
funciones deben definirse antes de utilizarse.
Una función puede ser invocada desde una sentencia
de otra función. Una función puede devolver un valor a
la sentencia que la ha llamado. El tipo de dato se indica
en la definición de la función; en el caso de no indicarse
nada se entiende que es un int8 y en el caso de no
devolver un valor se debe especificar el valor VOID. La
función, además de devolver un valor, puede recibir
parámetros o argumentos.
Definición de una función
Tipo_Dato Nombre_Función(tipo_dato parámetro1, … tipo_dato parámetroN)
{
definición variables locales;
seentencias;
return (expresión);
}
Ejemplos
int8 Suma (int8 a, int8 b)
{
Suma = a + b;
return (Suma);
}
Ejemplos
int16 fact_i ( int16 v )
{
int16 r = 1, i = 1;
while ( i <= v ) {
r = r * i;
i = i + 1;
}
return r;
}
Estructura IF - ELSE
if (expresión_1)
sentencia_1;
[else if (expresión_2)
sentencia_2;]
[else
sentencia_3;]
Nota: Si son varias sentencias a ejecutar en una
opción, se deben enmarcar con { y }.
Estructura SWITCH
switch(selector)
{
case Etiqueta A:
Acción A;
break;
case Etiqueta B:
Acción B;
break;
case Etiqueta n:
Acción n;
break;
[default:
Excepción;
break;]
}
Tipos de Ciclos
• WHILE (Entrada asegurada)
• FOR (Ciclo controlado por contador)
• DO … WHILE (Hacer mientras)
WHILE
Ciclo de Entrada Asegurada
La sintaxis es la siguiente:
while(condición)
Acción;
Funciona de la siguiente manera: primero
evalúa la condición, si da como resultado cierta
realiza la acción, luego vuelve a evaluar la
condición, si su resultado es falso, se sale del
ciclo y continúa con la ejecución del programa
FOR
Ciclo Controlado por contador.
En algunas ocasiones, sabemos a ciencia cierta el número de
veces que se tiene que repetir una misma acción o bloque de
acciones. Y para ello es que nos sirve, esta estructura. Su sintaxis es
la siguiente:
for( valor inicial; condición; incremento)
accion;
Donde:
Valor inicial: es el valor con el cual inicializamos nuestra variable de
control.
Condición: si la cumple, ejecuta la acción o acciones, sino cumple
la condición, se sale del ciclo.
Incremento; que puede ser positivo o negativo (decremento).
DO … WHILE
Es te ciclo funciona de la siguiente manera, realiza la acción o
conjunto de acciones, luego evalúa una condición de resultar cierta
vuelve a realizar las acciones. Cuando sea falsa, se sale del ciclo. Su
sintaxis es:
do {
sentencia;
.
.
} while(condición);
La diferencia fundamental, entre el ciclo while y do...while, es que en
este ultimo, las sentencias se realizarán por lo menos una vez, en
cambio, con while, solo se ejecutarán mientras se cumpla la condición,
en algunos casos nunca se ejecutarían.
De forma generalizada, la estructura de un programa en C tiene el siguiente aspecto:
declaraciones globales
funcion_1() {
variables locales a funcion_1;
bloque de sentencias;
llamada a otras funciones;
funcion_n() {
…
}
main() {
variables locales;
bloque de sentencias;
llamadas a las funciones;
}
/* Programa que hace parpadear un led en RB7 cada segundo */
#include <16F877.H> /* tipo de PIC */
#use delay( clock = 4000000 ) /* reloj de 4 MHz */
#byte puerto_b = 06 /* dirección del puerto B */
void main( void )
{
set_tris_b( 0x00 ); /* puerto B como salida */
puerto_b = 0; /* apaga todos los led */
do{
bit_set( puerto_b, 7 ); /* enciende el led RB7 */
delay_ms( 500 ); /* espera tiempo */
bit_clear( puerto_b, 7); /* apaga el led */
delay_ms( 500 ); /* retardo de tiempo */
} while( TRUE ); /* Repetir siempre */
}
Directivas
* #DEVICE chip, permite definir el PIC con el
que se realizará la compilación.
#device PIC16F877
* #FUSES opciones, la cual permite definir la
palabra de configuración para programar un PIC.
Opciones:
XT,NOWDT,WDT,NOPUT,PUT,PROTECT,NOPROTECT
#fuses XT, NOWDT, PUT, NOPROTECT
Directivas
• #INCLUDE ¨ archivo¨, permite incluir un
archivo al programa.
#include <16F877.h>
• #USES DELAY (CLOCK = speed), permite definir
la frecuencia del oscilador del PIC. Se puede
utilizar M, MHZ, K y KHZ para definir la
frecuencia.
#use delay (clock = 4000000)
Gestión de puertos en C
En lenguaje C se pueden gestionar los puertos de dos
formas:
1.- Se declaran los registros TRISx y PORTx definiendo
su posición en memoria RAM como variables de C.
2.- Utilizando las directivas específicas del compilador:
#USE FAST_IO
#USE FIXED_IO
#USE STANDARD_IO
1.- A través de la RAM
Se definen los registros PORTx y TRISx como bytes
con su dirección en memoria RAM
#BYTE variable=constante
Ejemplos:
#BYTE TRISA = 0x85 // variable TRISA en 85h
#BYTE PORTA= 0x05 // variable PORTA en 05h
#BYTE TRISB = 0x86 // variable TRISB en 86h
#BYTE PORTB= 0x06 // variable PORTB en 06h
#BYTE TRISC = 0x87 // variable TRISA en 87h
#BYTE PORTC= 0x07 // variable PORTA en 07h
Una vez definidas estas variables, se pueden configurar y
controlar los puertos a través de lo siguiente:
TRISA = 255; // 8 terminales de entrada
TRISB = 0; // 8 terminales de salida
TRISC = 0x0F; // 4 terminales de entrada
y 4 terminales de salida
Escritura en los puertos:
PORTC = 10; // salida en puerto C será 00001010
Lectura de puertos:
Valor = PORTA; // Asigna el valor del puerto A a la
variable Valor
Funciones para operar bits en C
bit_clear(variable,bit); // pone a 0 un bit(0-7) de la
variable.
bit_set(variable,bit); // pone a 1 un bit(0-7) de la variable.
bit_test(variable,bit); // muestrea el bit(0-7) de la variable.
swap(variable); // Intercambia los 4 MSB con los 4 LSB.
Ejemplos:
bit_set(PORTC,4); // ¨saca¨ un 1 por la terminal RC4
if(bit_test(PORTB,0)==1) bit_clear(PORTB,1);
// si RB0 es 1 borra RB1
Ejemplo
#include <16F876.h>
#fuses XT,NOWDT
#use delay (clock=4000000) // reloj de 4 MHz
#BYTE TRISB = 0x86 // TRISB es 86h
#BYTE PORTB = 0x06 // PORTB es 06h
#BYTE OPTION_REG = 0x81 // OPTION_REG es 81h
void main() {
bit_clear(OPTION_REG,7); // Habilitación Pull-up
bit_set(TRISB,0); // RB0 como entrada
bit_clear(TRISB,1); // RB1 como salida
bit_clear(PORTB,1); // Apaga salida RB1
while (1) {
if (bit_test(portb,0) == 1) // Si RB0 es 1
bit_clear(portb,1); // apaga salida RB1
else // si RB0 es 0
bit_set(portb,1); // enciende RB1
}
}
2.- A través de las directivas
Funciones:
output_X(valor); // por el puerto correspondiente saca el valor
(0 – 255)
input_X(); // se obtiene el valor en el puerto
correspondiente
set_tris_X(valor); // carga el registro TRISx con el valor (0 – 255)
port_b_pullups(valor); // Si valor = TRUE, habilita las resistencias
pull-up del puerto B.
// Si valor = FALSE, deshabilita las resistencias
pull-up del puerto B
get_trisX(); // devuelve el valor del registro TRISx
Las funciones output_X() e input_X() dependen de la directiva tipo #USE
*_IO que esté activa
#USE FAST_IO(PUERTO)
#include <16F876.h>
#fuses XT,NOWDT
#use delay (clock=4000000) // reloj de 4 MHz
#use fast_io(B) // hay que definir los TRISx
void main() {
port_b_pullups(TRUE); // Habilitación Pull-up
set_tris_B(0x01); // solo RB0 como entrada
output_low(PIN_B1); // Apaga salida RB1
while (1) {
if (input(PIN_B0) == 1) // Si RB0 es 1
output_low(PIN_B1); // apaga salida RB1
else // si RB0 es 0
output_high(PIN_B1); // enciende RB1
}
}
#USE STANDARD_IO(PUERTO)
#include <16F876.h>
#fuses XT,NOWDT
#use delay (clock=4000000) // reloj de 4 MHz
#use standard_io(B) // Configura los TRISx
void main() {
port_b_pullups(TRUE); // Habilitación Pull-up
output_low(PIN_B1); // Apaga salida RB1
while (1) {
if (input(PIN_B0) == 1) // Si RB0 es 1
output_low(PIN_B1); // apaga salida RB1
else // si RB0 es 0
output_high(PIN_B1); // enciende RB1
}
}
#USE FIXED_IO(PUERTO_OUTPUTS= pin*)
#include <16F876.h>
#fuses XT,NOWDT
#use delay (clock=4000000) // reloj de 4 MHz
#use fixed_io(b_outputs=pin_b1) // Solo se incluyen las salidas
void main() {
port_b_pullups(TRUE); // Habilitación Pull-up
output_low(PIN_B1); // Apaga salida RB1
while (1) {
if (input(PIN_B0) == 1) // Si RB0 es 1
output_low(PIN_B1); // apaga salida RB1
else // si RB0 es 0
output_high(PIN_B1); // enciende RB1
}
}
LCD
El compilador de C incluye un archivo (driver) que permite
trabajar con un LCD del tipo HD44780. El archivo es LCD.C y debe
llamarse como un #include.
Funciones:
lcd_init();
Es la primera función que debe ser llamada. Borra el LCD y lo
configura en el formato de 4 bits, con dos líneas y con caracteres
de 5x8 puntos, en modo encendido, cursor apagado y sin
parpadeo.
lcd_gotoxy(byte x, byte y);
Indica la posición de acceso al LCD (columna,línea)
lcd_getc(byte x, byte y);
Lee el carácter de la posición (x,y).
lcd_putc(char S);
S es una variable de tipo char. Esta función
escribe la variable en la posición
correspondiente. Si, además, se indica:
\f se limpia el LCD
\n el cursor va a la posición(1,2)
\b el cursor retrocede una posición
El compilador de C ofrece una función más
versátil para trabajar con el LCD:
printf(string)
printf(cstring, values…)
printf(fname, cstring, values…)
donde
string Es una cadena o un array de caracteres
values Es una lista de variables separadas por
comas
fname Es una función
El driver LCD.C está pensado para trabajar
con el PORTD o el PORTB. Por defecto, utiliza
el PORTD a menos que le indiquemos lo
contrario mediante:
#define use_portB_lcd TRUE
esta instrucción está incluida en el archivo,
solo modificamos el comentario o no.
#include <16F876.h>
#fuses XT,NOWDT
#use delay(clock= 4000000)
#include <lcd.c>
#use standard_io(A)
#use standard_io©
void main() {
char Estado='P';
lcd_init();
while (1) {
if (input(PIN_A0) && Estado != 'D') { //Detecta A0 y NO está girando a la Derecha
lcd_gotoxy(1,1); printf(lcd_putc, "Izquierda");
output_low(PIN_C0);output_high(PIN_C1); Estado = ‘I’; // Giro a la Izquierda
}
if (input(PIN_A1) ) { //Detecta A1
lcd_gotoxy(1,1); printf(lcd_putc, "PARO ");
output_low(PIN_C0);output_low(PIN_C1); Estado = 'P'; // PARO
}
if (input(PIN_A2) && Estado != 'I') { //Detecta A2 y NO está girando a la Izquierda
lcd_gotoxy(1,1); printf(lcd_putc, "Derecha ");
output_high(PIN_C0);output_low(PIN_C1); Estado = ‘D’; // Giro a la Derecha
}
} // Fin del While
} // Fin del main