exemplos ccs
DESCRIPTION
TRANSCRIPT
PROGRAMAS DE EXEMPLOS DO COMPILADOR CCS
1. Exemplos de xestión de portos 1.1. Xestión de portos en C
1.1.a) Xestión a través da memoria RAM
A configuración dos portos correspondentes dos PIC faise a través dos rexistros especiais TRISX, que
determinan os pins dos portos correspondentes que serán utilizados como entradas (1 no bit de
configuración) ou como saídas (0 no bit de configuración). Os valores lense ou sácanse a través dos portos
correspondentes PORTX.
Unha forma de controlar a configuración e os valores recibidos/transmitidos polos portos é definindo
a súa posición de memoria como unha variable da función a través da directiva #BYTE.
Logo podemos escribir no porto ou ler do porto un valor. Tamén podemos manexar independentemente os
bits.PORTX = valor // Saca polo porto X os bits correspondentes a valor
valor = PORTX // Asigna o dato do porto á variable valor
bit_clear (var, bit) // Pon 0 no bit especificado da variable
bit_set (var, bit) // Pon 1 no bit especificado da variable
bit_test (var, bit) // Recolle o valor do bit especificado da variable
swap (var) // Intercambia os 4 bits de maior peso (nibble superior) cos de menor peso (nibble
// inferior)
Exemplo 1: Terminal RB0 como entrada, RB1 como saída. O valor da entrada é reflectido na saída.
Para poder manexar a entrada con un interruptor conectado ou non a terra debemos activar
as resistencias internas de pull-up para obter un nivel alto cando o interruptor estea
desconectado.
Solución 1: Mediante o manexo das direccións de memoria RAM.
#include <16F84A.h> // O PIC empregado
#fuses XT, NOWDT // Fusibles: oscilador cristal (<=4 MHz)
// sen activar can gardián
#use delay(clock=4000000) // Velocidade do reloxo 4 MHz
#BYTE TRISB = 0x86 // Posición na RAM do rexistro de configuración do porto B
#BYTE PORTB = 0x06 // Posición do porto B na RAM
#BYTE OPTION_REG = 0x81 // Posición deste rexistro, para activar pull-up en B
void main () { bit_clear (OPTION_REG,7); // Habilitación do pull-up en B
bit_set (TRISB,0); // Pin 0 de configuración a 1: entrada
bit_clear (TRISB,1); // Pin 1 de configuración a 0: saída
bit_clear (PORTB,1); // Sacamos un 0 polo pin 1 do porto B
while (1) { // Bucle infinito
if (bit_test(PORTB,0) == 1) // Comprobamos se o bit 0 vale 1 (RB0==1??)
bit_clear(portb,1); // Se vale 1, sacamos un 0 pola saída (bit 1)
else bit_set(portb,1); // Se vale 0 sacamos un 1 polo bit 1 do porto B
}}
1.1.b) Xestión mediante as directivas do compilador
Solución 2: Mediante as directivas do compilador de C.
O compilador ten funcións especificas para traballar cos portos:
output_X (valor) // Saca un valor binario de 8 bits polo porto X (0-255)
input_X (valor) // Obtén o valor do porto X
set_tris_X (valor) // Configura o rexistro TRISX poñéndolle o valor (0-255)
port_b_pullups (valor) // valor=TRUE habilita pull-ups; valor=FALSE(=0) desabilítao
get_trisX() // Devolve o valor do rexistro TRISX
Ademais podemos manexar independentemente os pins dos portos (definidos mediante parámetros
no ficheiro de encabezamento do chip correspondente) con certas funcións:
output_low(pin) // Pon o pin a 0
output_high(pin) // Pon o pin a 1
output(pin,valor) // Pon o pin ao valor especificado
output_toggle(pin) // Complementa o valor do pin
output_float(pin) // Pin en drenador aberto, saída flotante
input_state(pin) // Le o valor do pin, sen modificar o sentido do terminal
input(pin) // Le o valor do pin e pono como entrada dependendo do tipo #USE *_IO usado
#USE FAST_IO (PORTO)Utilízase o porto correspondente para sacar valores con ouput_x() ou lelos con input_x() pero sen
modificar a configuración do porto. Os rexistros TRIS de configuración deben estar correctamente definidos.
#include <16F84A.h> // O PIC empregado
#fuses XT, NOWDT // Fusibles: oscilador cristal (<=4 MHz)
// sen activar can gardián
#use delay(clock=4000000) // Velocidade do reloxo 4 MHz
#use fast_io (B) // Forma de configurar porto B
void main () { port_b_pullups (TRUE); // Habilitación do pull-up en B
set_tris_B(0x01); // Pin 0=1 (entrada); pin 1=0 (saída)
output_low(PIN_B1); // Sacamos un 0 polo pin 1 do porto B
while (1) { // Bucle infinito
if (input(PIN_B0) == 1) // Comprobamos se o bit 0 vale 1 (RB0==1??)
output_low(PIN_B1); // Se vale 1, sacamos un 0 pola saída (bit 1)
else output_high(PIN_B1); // Se vale 0 sacamos un 1 polo bit 1 do porto B
}}
#USE STANDARD_IO(PORTO)
O compilador modifica o TRIS correspondente para asegurarse de que os terminais usados estean
correctamente configurados: saída coa función output_x() e entrada coa función input_x(). É a directiva que
se utiliza por defecto.
#include <16F84A.h> // O PIC empregado
#fuses XT, NOWDT // Fusibles: oscilador cristal (<=4 MHz)
// sen activar can gardián
#use delay(clock=4000000) // Velocidade do reloxo 4 MHz
#use standard_io (B) // Forma de configurar porto B
void main () { port_b_pullups (TRUE); // Habilitación do pull-up en B
// Agora non fai falla configurar o TRISB
output_low(PIN_B1); // Sacamos un 0 polo pin 1 do porto B
while (1) { // Bucle infinito
if (input(PIN_B0) == 1) // Comprobamos se o bit 0 vale 1 (RB0==1??)
output_low(PIN_B1); // Se vale 1, sacamos un 0 pola saída (bit 1)
else output_high(PIN_B1); // Se vale 0 sacamos un 1 polo bit 1 do porto B
}}#USE FIXED_IO(PORTO_OUTPUTS=pin*, ...)
Na directiva só se indican os terminais de saída e o compilador xera o código de configuración
correspondente, independentemente de que as operacións realizadas sexan de entrada ou de saída.
#include <16F84A.h> // O PIC empregado
#fuses XT, NOWDT // Fusibles: oscilador cristal (<=4 MHz)
// sen activar can gardián
#use delay(clock=4000000) // Velocidade do reloxo 4 MHz
#use fixed_io (b_outputs=pin_b1) // Só pin 1 do porto B como saída
void main () { port_b_pullups (TRUE); // Habilitación do pull-up en B
// Agora non fai falla configurar o TRISB
output_low(PIN_B1); // Sacamos un 0 polo pin 1 do porto B
while (1) { // Bucle infinito
if (input(PIN_B0) == 1) // Comprobamos se o bit 0 vale 1 (RB0==1??)
output_low(PIN_B1); // Se vale 1, sacamos un 0 pola saída (bit 1)
else output_high(PIN_B1); // Se vale 0 sacamos un 1 polo bit 1 do porto B
}}
1.1.c) Xestión mediante punteiros
Solución 3: Mediante a utilización de punteiros dirixidos ás posicións de memoria correspondentes.
Os punteiros débense definir como enteiros (int*).
#define TRISA (int*) 0x85
#define PORTA (int*) 0x05
Os rexistros manéxanse co operador de indirección *.
int valor;
valor = *portoa;
Os terminais lense e escríbense utilizando operacións lóxicas con bits.
*portoa != 0b0000001; // RA0 = 1 mediante OR (resto dos bits do porto non cambian
*portoa &= 0b11111101; // RA1 = 0 mediante AND (resto dos bits do porto non cambian
bit0 = (*portoa & 0b00000001); // Lemos o bit 0 do portoa
O exemplo quedaría:
#include <16F84A.h> // O PIC empregado
#fuses XT, NOWDT // Fusibles: oscilador cristal (<=4 MHz)
// sen activar can gardián
#use delay(clock=4000000) // Velocidade do reloxo 4 MHz
#define TRISB (int*) 0x86 // Contido de TRISB na dirección 0x86
#define PORTB (int*) 0x06 // Contido de PORTB na dirección 0x06
#define OPTION (int*) 0x81 // Contido de OPTION na dirección 0x81
void main () { *option &= 0b0111111; // Habilitación do pull-up en B poñendo 0 no bit 7 do
// OPTION_REG (variable option)
*trisb = 0x01; // RB0 entrada, RB1 (e o resto) saída
*portb = 0x00; // Sacamos un 0 polo pin 1 do porto B
while (1) { // Bucle infinito
if (*portb & 0x01) // Comprobamos se o bit 0 vale 1 (RB0==1??)
*portb = 0x00; // Se vale 1, sacamos un 0 pola saída (bit 1)
else *portb = 0x02; // Se vale 0 sacamos un 1 polo bit 1 do porto B
}}
1.1.d) Exemplo utilización
Exemplo 2: Realizar un contador de 0 a 99 con dous displays de 7 segmentos de cátodo común. O
díxito das decenas debe estar apagado de 0 a 9.
#include <16f84A.h>#use delay (clock = 4000000)#fuses XT, NOWDT, NOPROTECT, NOPUT#use standard_io (A)#use standard_io (B)
byte CONST display[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};// O valor hexadecimal proporciona os 7 bits para activar os leds e ver os números de 0 a 9
main() {byte ud=0, dec=0;output_b(0);for (;;) {
for (dec=0;dec<10;dec++) { // Bucle externo para decenas
for (ud=0;ud<10;ud++) { // Bucle interno para unidades
output_a (0x02); // Alimentamos cun 0 visualizador de unidades
output_b(display[ud]); // Percorremos as unidades
delay_ms(50); // Parando un pouco en cada unha
if (dec==0) output_a(0x03); // Se non hai decenas non se ve 0
else output_a (0x01); // Se as hai alimentamos o visulizador decenas
output_b(display[dec]); // Percorremos as decenas
delay_ms(50); // Parando un pouco en cada una
}}
}}
1.2. Xestión de dispositivos de entrada e saída
1.2.a) LCD
O compilador inclúe ficheiros en C para traballar con visualizadores LCD. Este ficheiro pódese
modificar para adaptalo ás nosas necesidades.NOTA: A MODIFICACIÓN DOS FICHEIROS DO COMPILADOR DEBE REALIZARSE CON CAUTELA, COIDANDO DE FACER COPIA DO FICHEIRO ORIXINAL E
GARDAR CON OUTRO NOME O ARQUIVO MODIFICADO.
Un dos arquivos é lcd.c, no cartafol drivers, e debe incluírse para a compilación no noso ficheiro fonte.
Dentro deste arquivo existen varias funcións definidas que facilitan o manexo do visualizador.
lcd_init ();Configúrao para traballar con 4 bits, dúas liñas e tamaño de 5x8 puntos por caracter, sen visualización do
cursor e con xestión automática do punteiro de direccións. As catro liñas de funcionamento serven para a
xestión e para a escritura e lectura de datos. Débese chamar en primeiro lugar.
lcd_gotoxy (byte x, byte y);Indica a posición de traballo no LCD: (1,1) é a primeira columna da primeira liña e (2,1) é a primeira columna
da segunda liña. O LCD ten 2 liñas e 16 caracteres.
lcd_getc (byte x, byte y);Le o contido da posición (x,y).
lcd_putc (char s);Escribe a variable tipo char na posición correspondente. Permite os seguintes caracteres especiais:
\f limpa o LCD
\n cursor á posición (1,2)
\b o cursor retrocede unha posición
Tamén podemos utilizar unha función semellante a printf () do C para utilizar o LCD.
printf (string) // string é unha cadea ou array de caracteres
printf (cstring, valores ...) // valores é unha lista de variables separadas por comas
printf (fname, cstring, valores ...) // fname é unha función
A función permite a utilización de formato de presentación de variables tipo %nt, onde n é opcional e indica
a cantidade de caracteres e/ou decimais que se representan e t indica o tipo de formato para a variable.NOTA: PARA MAIOR INFORMACIÓN SOBRE AS FUNCIÓNS ESPECÍFICAS DO COMPILADOR PÓDESE CONSULTAR A AXUDA DO MESMO, NO APARTADO BUILT-
IN FUNCTIONS.
Exemplos de utilización:
byte x, y, z;
printf (“Ola”);
printf (“Valor => %2x\n\r”, get_rtcc () );
printf (“%2u %X %4x\n\r”, x, y, z);
printf (LCD_PUTC, “n=%u”, n);
O ficheiro está configurado para traballar co porto D, pero non comentar unha das liñas permite utilizar o
porto B. A utilización doutros portos require modificacións máis amplas. A conexión do porto utilizado cos
pins do LCD está tamén indicada no arquivo.
Exemplo 3: Realizar un menú de control mediante dous pulsadores: un permite seleccionar entre os tres
elementos do menú que aparecen no LCD e o outro executa a función asociada a ese
elemento (modificar unha das saídas para acender o LED asociado).
#include <16f84A.h>#fuses XT, NOWDT#use delay (clock=4000000)#include <lcd_b.c> // O ficheiro modificado para traballar co porto B
#use fast_io (A)#use standard_io (B)
enum funciones {med, cal, ini}; // Asigna un valor a cada elemento
// med = 0, cal = 1, ini = 2
void medir (void){set_tris_a (0x03); // Pins 0 e 1 entradas, resto do porto A saídas
output_toggle(pin_A2); // A función asociada a medir é cambiar o estado do pin 2
}
void calibrar (void){set_tris_a (0x03); // Pins 0 e 1 entradas, resto do porto A saídas
output_toggle(pin_A3); // A función asociada a calibrar é cambiar o estado do pin 3
}
void inicializar (void){set_tris_a (0x03);output_toggle(pin_A4); // A función asociada a calibrar é cambiar o estado do pin 4
} /* Coidado, este pin é de saída en drenador aberto e necesita resistencia de pull-up externa para mostrar un nivel alto */
void run_func (int numfunc){
switch (numfunc){ // Segundo o valor de numfunc realizamos a función asociada
case med: // med vale 0, coa orde enum
medir();break;
case cal: // cal vale 1, pola orde enum
calibrar ();break;
case ini:inicializar (); // ini vale 2, pola orde enum
break;}
}
void main () {char item; // Percorremos o menú secuencialmente
char n_menus = 3; // Para volver ao primeiro elemento ao rematalos
lcd_init(); // Primeira función para traballar co LCD
while(1) {
if (input(pin_A0 == 1)) { // A cada pulsación no pin A0
item++; // avanzamos un elemento de menú
delay_ms (300); // Retardo para evitar rebotes
lcd_putc ('\f'); // Borramos LCD
}if (item > (n_menus-1)) // Rematamos os elementos do menú?
item = 0; // Se os rematamos volvemos ao principio
switch (item) { // Poñemos no LCD o elemento de menu
case 0:lcd_gotoxy (1,1);printf (lcd_putc, “MEDIR”);lcd_gotoxy (1,1);break;
case 1:printf (lcd_putc, “CALIBRAR”);lcd_gotoxy (1,1);break;
case 2:printf (lcd_putc, “INICIALIZAR”);lcd_gotoxy (1,1);break;
}if (input (pin_A1) == 1) { // Poñendo A1 en alta ...
delay_ms (200); // retardo para evitar rebotes ...
run_func (item); // Executamos a función asociada a ese elemento do menú
}}
}
1.2.b) LCD GRÁFICO
Para o manexo de LCD gráficos o compilador proporciona varios arquivos para utilizalos como
controladores: glcd.c, graphics.c, hdm64gs12.c. Tamén podemos buscar controladores para os
visualizadores en internet.
A maioría dos controladores indican os terminais que se utilizan para o manexo, as conexións
necesarias cos portos do PIC e as funcións que pode manexar: liñas, círculos, rectángulos, ... e os
parámetros necesarios para utilizalas. É conveniente botar unha ollada a estes arquivos, e tamén editalos
para adaptalos ás nosas necesidades.
Para escribir as letras no modo gráfico utilízase unha matriz na que se definen mediante valores
hexadecimais os pixels que se deben acender en cada unha das columnas coas que se definen os
caracteres (no noso caso caracteres de 5x7, cinco columnas de 7 puntos). Podemos modificar estes valores
para cambiar o aspecto dos caracteres ou definir outros novos (por exemplo para poder manexar Ñ, ñ, Á, á
ou outros non incluídos no controlador).
O visualizador ten dous terminais de selección CS1 e CS2 , que manexan as dúas metades
verticais en que está dividido. No controlador utilizado debemos cambiar a asignación dos pins que os
manexan para lograr unha correcta visualización.
As funcións definidas nestes controladores son as siguintes:
glcd_init (mode)Acende se mode é ON ou apaga se mode é OFF o LCD. Debe ser a primeira que se chama.
glcd_pixel(x,y,cor)Pode activarse ou desactivarse para que o pixel apareza iluminado ou non (só admite dúas cores).
glcd_fillScreen(cor)Enche o visualizador coa cor seleccionada.
glcd_update()Escribe o contido da RAM no LCD.
glcd_line(x1,y1,x2,y2,cor)Debuxa unha liña entre o primeiro punto e o segundo da cor indicada (ON ou OFF)
glcd_rect(x1,y1,x2,y2,fill,cor)Debuxa un rectángulo dando o vértice superior dereito e o inferior esquerdo, o recheo e a cor.
glcd_bar(x1,y1,x2,y2,ancho,cor)Debuxa unha barra, co ancho especificado en pixels.
glcd_circle(x,y,radio,fill,cor)Debuxa un círculo dando o centro e o radio, recheo ou non e coa cor activada ou non.
glcd_text57(x,y,textptr,tamaño,cor)Comezando no punto indicado, escribe o texto ao que apunta textptr (punteiro). O texto debe rematar co
caracter nulo e pódese escoller o tamaño (número enteiro) e activar ou non a cor.
Exemplo 4: Visualizar nun LCD gráfico o estado das entradas do porto A, mediante un rectángulo
recheo ou non segundo a entrada correspondente sexa 1 ou 0.
#include <18F452.h>#fuses HS,NOWDT,NOPROTECT,NOLVP#use delay(clock=8000000)#include <HDM64GS12.c>#include <graphics.c>#use standard_io(a)
void main() {CHAR A5[ ]="A5";CHAR A4[ ]="A4";CHAR A3[ ]="A3";CHAR A2[ ]="A2";CHAR A1[ ]="A1";CHAR A0[ ]="A0";CHAR IN[ ]="PUERTO A";glcd_init(ON);glcd_text57(33, 30,A5, 1, 1);glcd_text57(49, 30,A4, 1, 1);glcd_text57(65, 30,A3, 1, 1);glcd_text57(81, 30,A2, 1, 1);
glcd_text57(97, 30,A1, 1, 1);glcd_text57(113, 30,A0, 1, 1);glcd_text57(30,5,IN, 2, 1);
while(1){if (input_state(PIN_A5)==0) glcd_rect(32,40,46,60,1,1);else glcd_rect(32,40,46,60,1,0);glcd_rect(32,40,46,60,0,1);if (input_state(PIN_A4)==0)glcd_rect(48,40,62,60,1,1);else glcd_rect(48,40,62,60,1,0);glcd_rect(48,40,62,60,0,1);if (input_state(PIN_A3)==0) glcd_rect(64,40,78,60,1,1);else glcd_rect(64,40,78,60,1,0);glcd_rect(64,40,78,60,0,1);if (input_state(PIN_A2)==0) glcd_rect(80,40,94,60,1,1);else glcd_rect(80,40,94,60,1,0); glcd_rect(80,40,94,60,0,1);if (input_state(PIN_A1)==0) glcd_rect(96,40,110,60,1,1);else glcd_rect(96,40,110,60,1,0);glcd_rect(96,40,110,60,0,1);if (input_state(PIN_A0)==0) glcd_rect(112,40,126,60,1,1);else glcd_rect(112,40,126,60,1,0);glcd_rect(112,40,126,60,0,1);delay_ms(400);}
}
Os controladores de LCD utilizados neste exemplo usan os portos B e C para o control e o porto D
para os datos. A simulación é correcta, pero no LCD real debemos cambiar os pins de selección das cúas
metades CS1 e CS2 . Tamén adaptamos o controlador para manexar o visualizador mediante os
portos B e D. Coma sempre, facemos copia dos controladores orixinais antes de modificalos.
1.2.c) TECLADO
O compilador inclúe tamén controladores para manexar teclados, en concreto un teclado matricial de 3x4. O
arquivo kbd.c incorpora as seguintes funcións:
kbd_init()Inicializa o sistema. Debe chamarse antes do uso do teclado.
kbd_getc()Devolve o valor da tecla pulsada. O valor atópao na táboa que ten programada en forma de matriz de 4x3
(catro filas e tres columnas).
O controlador utiliza o porto D para manexar o teclado, pero pódese modificar facilmente para
traballar co porto B. Tamén se poden configurar as conexións de filas e columnas e o patrón de teclas
asignadas. Para o funcionamento correcto o porto utilizado debe ter activado o pull-up para ler un nivel alto
cando non se pulsa ningunha tecla.
As pulsacións das teclas recóllense como caracteres ASCII. Para poder operar con elas como
números debemos restarlle o valor decimal 48 (código ASCII do número 0).
Exemplo 5: Deseñar un sistema que permita abrir unha porta ao introducir o código correcto a través
dun teclado. Indicar mediante unha pantalla LCD a apertura ou non da porta.
No exemplo gardaremos a clave da porta na memoria EEPROM, para poder modificala de forma doada.
#include <16f84A.h> #fuses xt, nowdt, noput, noprotect #use delay (clock = 4000000)
#use standard_io(A)
#include <lcd_b.c> #include <kbd_b.c> #include <stdlib.h> #rom 0x2100={'1', '2', '3'} // Posición de inicio da EEPROM e datos gardados
void main() {
char k; // Garda a pulsación
int i; // Percorre os elementos da matriz
char data[3], clave[3]; // Matrices para gardar as pulsacións e a clave
lcd_init(); kbd_init(); port_b_pullups(TRUE); // Pull-up activo no porto que recibe pulsacións do teclado
while (TRUE){ i=0; printf(lcd_putc, "\fPulsar tecla 1\n"); // Indicamos recepción primeiro número
while (i<=2){ // O índice de matriz para o primeiro número é 0
k=kbd_getc(); if (k!=0){ // Recollemos pulsación?
data[i]=k; // Asignamos valor na matriz
i++; // Seguinte índice
printf(lcd_putc, "\fPulsar tecla %u\n", i+1); }
} for (i=0;i<=2;i++)
clave[i]=read_eeprom(i); // Lemos ROM e asignámoslle un elemento da matriz
if ((data[0]==clave[0])&&(data[1]==clave[1])&&(data[2]==clave[2])) { // Comprobamos se o introducido coincide co gardado
printf(lcd_putc, "\fAbrindo porta\n"); output_high(pin_A0); // Se coincide abrimos porta
delay_ms(500); output_low(pin_A0); // Pulso en alta de 0,5 s para excitar o relé
} else printf (lcd_putc, "\fPorta pechada\n");
delay_ms(1000); }
}Neste exemplo tanto o teclado como o visualizador LCD están manexados co porto B, mentres a saída se
toma do pin 0 do porto A.
Exemplo 6: Elaborar un programa que permita sacar por un pin un sinal PWM proporcional ao valor
introducido a través dun teclado. O sinal PWM será utilizado para controlar a velocidade dun
motor, de maneira proporcional ao valor introducido.
Utilizamos unha variable tipo int ou char para definir os tempos en alta e en baixa do sinal, de maneira que
o valor máximo sexa 255. Así, se o ciclo de traballo é 100 %, o valor do tempo en alta será 255 e o do
tempo en baixa será 0. Para indicar cun valor numérico entre 0 e 9 o ciclo de traballo deberiamos facerlle
corresponder o 100 % ao valor 9, e por tanto 2559
×N a cada valor N posible. Para non manexar valores
decimais (variables float), que ocupan moito espazo en memoria, utilizaremos en lugar de 2559 o valor
enteiro máis próximo: 28. Ao tempo en baixa corresponderalle o valor 255 - (tempo en alta).