11 ejercicios micros en c v2

71
1 EJERCICIOS DE MICROCONTROLADORES EN C

Upload: michael-santiago-simba

Post on 27-Dec-2015

339 views

Category:

Documents


7 download

TRANSCRIPT

Page 1: 11 Ejercicios Micros en C V2

1

EJERCICIOS DE

MICROCONTROLADORES EN C

Page 2: 11 Ejercicios Micros en C V2

2

ÍNDICE

1. Construcción de un simple secuenciador a LED

2. Secuencia de luces. Utilizando operadores aritméticos

3. Decodificador de Binario a 7 Segmentos 4. Teclado 4x3 LCD con el PIC 16F877A 5. Uso de la interrupción externa por RB0/INT

6. Uso de la Interrupción por cambio de estado en RB4-RB7

7. Uso del TMR0 como contador 8. Uso del TMR0 como temporizador

9. Uso del TMR0 como contador o temporizador 10. Uso del TMR1 como Temporizador

11. Uso de la memoria EEPROM interna

12. Control de varias interrupciones

13. Comunicación serie entre dos PICs con la USART

Page 3: 11 Ejercicios Micros en C V2

3

1. Construcción de un simple secuenciador a LED

Realizar un circuito para encender en forma secuencial un diodo LED en RB0, cuya frecuencia

de intermitencia es de 1 seg.

El circuito a ser realizado se presenta a continuación:

El código de este ejemplo en Assembler está disponible en el capítulo “Los Puertos Paralelos de

Entrada/Salida”, LED.ASM, y a continuación se muestra en lenguaje C: /****************************************************************************** * * * DESCRIPCIÓN: Frecuencia de intermitencia de 1 seg * * Led.c * * * ******************************************************************************/ #include <16F877.h> #fuses XT,NOWDT,NOPROTECT #use delay(clock=4000000) #use fixed_io(b_outputs = PIN_B0) void main() { while(1) { output_high(PIN_B0); delay_ms(1000); output_low(PIN_B0); delay_ms(1000); } }

Análisis del código LED.C

Se va analizar línea por línea el contenido del source LED.C, desde de la primera línea de

código.

o #include esta directiva de C hace que el compilador incluya en el archivo fuente el texto que contiene el archivo especificado, en este caso la directiva que permite

definir el PIC a utilizar.

Page 4: 11 Ejercicios Micros en C V2

4

#include <16F877.h>

o #fuses es una directiva del compilador C que permite definir la palabra de configuración del

PIC, esta directiva define qué fusibles deben activarse en el dispositivo cuando se programe.

En este caso se informa al compilador que se usa un reloj en modo XT, no Watch Dog y no

protegido el código:

#fuses xt,NOWDT,NOPROTECT

o La directiva #use delay sirve establecer el reloj con el cual va a trabajar el PIC.

#use delay(clock=4000000)

o La directiva #use fixed causará que el compilador genere código para hacer que un pin de

I/O sea entrada o salida cada vez que se utiliza.

#use fixed_io(b_outputs = PIN_B0)

NOTA: No son necesaria las directivas: #include, #fuses y #use delay, porque en el archivo

LED.H se configuran automáticamente al crear el proyecto.

o void main() es la función principal del programa, en el cual hay un lazo infinito que pone en

alto la salida RB0, a continuación hay un delay de 1 seg, y lo mismo para cuando la salida se

pone en bajo:

while(1) { output_high(PIN_B0); delay_ms(1000); output_low(PIN_B0); delay_ms(1000); }

Page 5: 11 Ejercicios Micros en C V2

5

2. Secuencia de luces. Utilizando operadores aritméticos

Este ejemplo simula 8 luces del coche fantástico; desplaza el bit que está a uno hacia la izquierda

y desplaza el bit a uno hacia la derecha, con lo que se tiene una secuencia.

Este es un ejemplo que sirve para ver cómo utilizar los operadores en los programas, una

multiplicación por 2 (en binario 10) hace desplazar el bit que está a uno hacia la izquierda, y al

dividir entre 2 se desplaza el bit a uno hacia la derecha.

Esquema del circuito:

RA0/AN02

RA1/AN13

RA2/AN2/VREF-/CVREF4

RA4/T0CKI/C1OUT6

RA5/AN4/SS/C2OUT7

RE0/AN5/RD8

RE1/AN6/WR9

RE2/AN7/CS10

OSC1/CLKIN13

OSC2/CLKOUT14

RC1/T1OSI/CCP216

RC2/CCP117

RC3/SCK/SCL18

RD0/PSP019

RD1/PSP120

RB7/PGD40

RB6/PGC39

RB538

RB437

RB3/PGM36

RB235

RB134

RB0/INT33

RD7/PSP730

RD6/PSP629

RD5/PSP528

RD4/PSP427

RD3/PSP322

RD2/PSP221

RC7/RX/DT26

RC6/TX/CK25

RC5/SDO24

RC4/SDI/SDA23

RA3/AN3/VREF+5

RC0/T1OSO/T1CKI15

MCLR/Vpp/THV1

U1

PIC16F877A

1

23

4

5

67

8

16

1514

13

12

1110

9

RN1

220

1

23

4

5

67

8

20

1918

17

16

1514

13

9

10

12

11

U2

LED-BARGRAPH-GRN

El código del ejemplo se muestra a continuación:

/****************************************************************************** * * * DESCRIPCIÓN: Coche fantástico (utilizando operadores matemáticos) * * Secuencia.c * * * ******************************************************************************/ #include <16F877.h> #use delay(clock=4000000) #fuses XT,NOWDT #byte puerto_b=0x06 void main() { set_tris_b(0x00); puerto_b=1; delay_ms(1000); while(true) { while(puerto_b<=0b01000000) {

Page 6: 11 Ejercicios Micros en C V2

6

puerto_b*=2; delay_ms(1000); } while(puerto_b>=0b00000010) { puerto_b/=2; delay_ms(1000); } } }

Comentario:

El código del ejemplo anterior con numeración en cada sentencia, para poder explicarlo:

• En la línea 4: #byte puerto_b=6 --> creamos un identificador llamado puerto_b para

referenciar el registro de proposito específico (SFR) con dirección 0x06. Esta es una

forma de acceder a los puertos del PIC, pero hay más. Se verán todas las formas cuando

se vea el tema de los punteros.

• En la línea 8: set_tris_b(0x00); --> configuramos el puerto b del PIC como salida digital.

• En la línea 9: puerto_b=1; --> asignamos el valor 1 (en binario 0b00000001) al puerto b,

o sea RB0=1 los demás bits del puero B a cero.

Page 7: 11 Ejercicios Micros en C V2

7

3. Decodificador de Binario a 7 Segmentos

Decodificador de binario a decimal, mostrando los resultados en un display de siete segmentos.

Circuito:

RA0/AN02

RA1/AN13

RA2/AN2/VREF-/CVREF4

RA4/T0CKI/C1OUT6

RA5/AN4/SS/C2OUT7

RE0/AN5/RD8

RE1/AN6/WR9

RE2/AN7/CS10

OSC1/CLKIN13

OSC2/CLKOUT14

RC1/T1OSI/CCP216

RC2/CCP117

RC3/SCK/SCL18

RD0/PSP019

RD1/PSP120

RB7/PGD40

RB6/PGC39

RB538

RB437

RB3/PGM36

RB235

RB134

RB0/INT33

RD7/PSP730

RD6/PSP629

RD5/PSP528

RD4/PSP427

RD3/PSP322

RD2/PSP221

RC7/RX/DT26

RC6/TX/CK25

RC5/SDO24

RC4/SDI/SDA23

RA3/AN3/VREF+5

RC0/T1OSO/T1CKI15

MCLR/Vpp/THV1

U1

PIC16F877A

123

45678

161514

131211109

RN1

RX8

0

1

1

Comentario:

Circuito sencillo que hace las funciones de un decodificador de binario a decimal de un dígito y

muestra los resultados en un display de siete segmentos de cátodo común, lo que quiere decir que

el PIC debe proporcionar valores positivos en su salida de la puerta B.

La relación entre las patillas del display y el valor de las letras de cada segmento es la siguiente:

luego se puede hacer una pequeña tabla de verdad donde se muestre por cada combinación en la

entrada que patillas del PIC se tienen que activar:

Page 8: 11 Ejercicios Micros en C V2

8

A2 A1 A0 Valor

Decimal Salidas Activas Valor

Hexadecimal 0 0 0 0 RB0,RB1,RB2,RB3,RB4,RB5 0x3F

0 0 1 1 RB1,RB2 0x06

0 1 0 2 RB0,RB1,RB3,RB4,RB6 0x5B

0 1 1 3 RB0,RB1,RB2,RB3,RB6 0x4F

1 0 0 4 RB1,RB2,RB5,RB6 0x66

1 0 1 5 RB0,RB2,RB3,RB5,RB6 0x6D

1 1 0 6 RB1,RB2,RB3,RB4,RB6 0x7D

1 1 1 7 RB0,RB1,RB2 0x07

La lógica del programa lo único que hace es chequear el estado de las entradas, en este caso

representadas por los interruptores A0, A1 y A2 y activar el conjunto de patillas

correspondientes en el PIC que alimentarán los ánodos del display.

El código del ejemplo se muestra a continuación:

/****************************************************************************** * * * DESCRIPCIÓN: Decodificador de binario a decimal * * Bin_Dec.c * * * ******************************************************************************/ #include <16F877.h> #use delay( clock=4000000 ) #fuses XT, NOWDT #byte port_A = 0x05 // Dirección del port A. #byte port_B = 0x06 // Dirección del port B. void main() { set_tris_b( 0x00 ); // Se configura port B como salida. set_tris_a( 0x3F ); // Se configura port A como entrada port_B = 0x00; // Ningún segmento a ON while( true ) { switch( port_A ) { case 0: port_B = 0x3F; break; case 1: port_B = 0x06; break; case 2: port_B = 0x5B; break; case 3: port_B = 0x4F; break; case 4: port_B = 0x66; break; case 5: port_B = 0x6D; break; case 6:

Page 9: 11 Ejercicios Micros en C V2

9

port_B = 0x7D; break; case 7: port_B = 0x07; break; } } }

Page 10: 11 Ejercicios Micros en C V2

10

4. Teclado 4x3 LCD con el PIC 16F877A Realizar un circuito para identificar la tecla presionada de un teclado matricial 4x3. La

visualización de la tecla se realiza en una LCD 16x2.

El circuito a ser realizado se presenta a continuación:

RA0/AN02

RA1/AN13

RA2/AN2/VREF-/CVREF4

RA4/T0CKI/C1OUT6

RA5/AN4/SS/C2OUT7

RE0/AN5/RD8

RE1/AN6/WR9

RE2/AN7/CS10

OSC1/CLKIN13

OSC2/CLKOUT14

RC1/T1OSI/CCP216

RC2/CCP117

RC3/SCK/SCL18

RD0/PSP019

RD1/PSP120

RB7/PGD40

RB6/PGC39

RB538

RB437

RB3/PGM36

RB235

RB134

RB0/INT33

RD7/PSP730

RD6/PSP629

RD5/PSP528

RD4/PSP427

RD3/PSP322

RD2/PSP221

RC7/RX/DT26

RC6/TX/CK25

RC5/SDO24

RC4/SDI/SDA23

RA3/AN3/VREF+5

RC0/T1OSO/T1CKI15

MCLR/Vpp/THV1

U1

PIC16F877A

D7

14

D6

13

D5

12

D4

11

D3

10

D2

9D

18

D0

7

E6

RW

5R

S4

VS

S1

VD

D2

VE

E3

LCD1LM016L

R1

10k

R2

10k

R3

10k

R4

10k

1 2 3

4 5 6

7 8 9

0 #

1 2 3

A

B

C

D

Se adjunta en el programa:

• -Librería flex_lcd: para el cambio de pines de la utilización de la LCD.

• -Librería kbd_lib: para la utilización de teclados matriciales 4x4.

El código del ejemplo se muestra a continuación:

/****************************************************************************** * * * DESCRIPCIÓN: Teclado 4x3 LCD con el Pic 16F877A * * teclado LCD.c * * * ******************************************************************************/ #include <16F877A.h> #fuses XT,NOCPD,NOWDT,NOPUT,NOLVP,NOBROWNOUT #use delay(clock=4M) #include <kbd_lib.c>

Page 11: 11 Ejercicios Micros en C V2

11

#include <lcd.c> #byte kbd_port_b = 0x08 void main() {

int k; kbd_init(); lcd_init(); lcd_putc("\fPresione...\n"); while(TRUE) { k=kbd_getc(); if(k!=0) { lcd_gotoxy(1,2); lcd_putc(k); } }

}

Comentarios:

En primer lugar se incluye las librerías para usar el teclado y el LCD, respectivamente:

#include <kbd_lib.c> #include <lcd.c>

A continuación se debe asignar el puerto en el cual se va a conectar el teclado, en este caso el

puerto D que tiene la dirección 0x08:

#byte kbd_port_b = 0x08

En el programa principal se realiza las siguientes acciones:

• Inicializar el teclado y el LCD:

lcd_init(); kbd_init();

• Luego se puede enviar mensajes al LCD, en este caso "\fPresione...\n", que limpia la

pantalla pone el mensaje Presione..., y luego cambia de línea:

lcd_putc("\fPresione...\n");

• Para recibir la información del teclado, se realiza en un lazo infinito mediante una

variable entera k, con la siguiente sentencia:

k=kbd_getc();

• Por último, se muestra la tecla pulsada en el LCD, fila 2 columna 1, mediante las

sentencias:

lcd_gotoxy(1,2); lcd_putc(k);

Page 12: 11 Ejercicios Micros en C V2

12

5. Uso de la interrupción externa por RB0/INT

En este ejemplo vamos a ver el uso de la interrupción externa a través de la patilla RB0/INT,

para ello vamos a utilizar el PIC 16f877, aunque si utilizas otro PIC de mayores prestaciones el

proceso será el mismo.

En cuanto a los pines del PIC la señal externa para producir la interrupción en el PIC será a

través de de la patilla RB0/INT y se podrá determinar por software que flanco de la señal

producirá la interrupción, el de subida (cuando la señal pasa de un nivel 0 a 1) ó el de bajada

(cuando pasa de 1 a 0).

Vamos a ver ahora los registros específicos (SFR) que nos proporciona el PIC para el control de

esta interrupción.

OPTION

#RBPU INTED TOCS TOSE PSA PS2 IPS1 PS0

Bit 7 Bit 0

INTCON

GIE PEIE T0IE INTE RBIE T0IF INTF RBIF

Bit 7 Bit 0

• INTED: flanco activo interrupción externa.

1 la interrupción se producirá en el flanco ascendente.

0 la interrupción se producirá en el flanco descendente.

• GIE: (Habilita las interrupciones globalmente). Este bit permite que cualquier

interrupción sea posible. Para poder usar cualquier interrupción hay que habilitarla

globalmente e individualmente.

• INTE: Permiso de interrupción por activación de la patilla RB0/INT

1 permite la interrupción

0 prohíbe la interrupción

• INTF: bit de señalización (solo lectura) de interrupción externa RB0/INT

Para ver como CCS gestiona estos recursos, vamos a crear el siguiente circuito en Proteus:

Page 13: 11 Ejercicios Micros en C V2

13

RA0/AN02

RA1/AN13

RA2/AN2/VREF-/CVREF4

RA4/T0CKI/C1OUT6

RA5/AN4/SS/C2OUT7

RE0/AN5/RD8

RE1/AN6/WR9

RE2/AN7/CS10

OSC1/CLKIN13

OSC2/CLKOUT14

RC1/T1OSI/CCP216

RC2/CCP117

RC3/SCK/SCL18

RD0/PSP019

RD1/PSP120

RB7/PGD40

RB6/PGC39

RB538

RB437

RB3/PGM36

RB235

RB134

RB0/INT33

RD7/PSP730

RD6/PSP629

RD5/PSP528

RD4/PSP427

RD3/PSP322

RD2/PSP221

RC7/RX/DT26

RC6/TX/CK25

RC5/SDO24

RC4/SDI/SDA23

RA3/AN3/VREF+5

RC0/T1OSO/T1CKI15

MCLR/Vpp/THV1

U1

PIC16F877A

R31k

R2330R

R4

330R

Q1BC547

D11N4007

RL112V

D21N4007

RL212V

L112V

12 V12 V

Alarma

Activación/Desactivación de Alarma

Sensor de Alarma

R.Potencia

Micro-Relé

X1CRYSTAL

R1330

C1

15p

C2

15p

El circuito es una alarma sencilla, lo que tiene que hacer es lo siguiente:

Cuando se pulse el sensor de alarma y no esté cerrado el interruptor de desactivación de alarma,

se pondrá a uno la patilla RB7 del PIC el cual polarizará directamente el transistor Q1, que a su

vez activará el Micro-relé RL1 (Relé auxiliar), este a su vez activará un relé de potencia que

activará una alarma conectada a 220V.

Cuando queremos activar una carga con una potencia importante, es necesario adaptar el circuito

de salida para poder controlar con los pocos miliamperios que nos da el PIC la carga que

queramos. Hay muchas formas de hacer esto, se puede utilizar un par de transistores en

configuración Darlington y atacar directamente el relé de potencia ó no utilizar relés y controlar

la carga por medio de un Triac de potencia, lo podrás hacer como quieras.

Una vez activada la alarma, permanecerá activa hasta que no la desactivemos por medio del

interruptor de desactivación de alarma (interruptor cerrado).

Ahora vamos a crear el programa en C, para ello vamos a crear un nuevo proyecto a través del

asistente, de esta forma vamos viendo las posibilidades que nos da esta herramienta,

configuraremos las opciones según se muestra en las siguientes figuras:

Page 14: 11 Ejercicios Micros en C V2

14

Los demás valores dejamos por defecto.

Una vez creada la plantilla con el asistente, añadiremos las siguientes líneas de código para hacer

nuestro programa funcional:

Page 15: 11 Ejercicios Micros en C V2

15

Nota: El asistente genera dos archivos de código fuente, el .h y el .c esta es la forma correcta de

trabajar en C, la famosa frase de divide y vencerás es perfectamente aplicable a la programación

en C, se sigue utilizando un solo archivo para todo el código es porque de momento los archivos

son muy pequeños y resulta más cómodo incluir una imagen en el sitio en vez de dos. Realmente

cuando tengamos un proyecto grande, estará formado por varios archivos .h y .c

Comentario del programa

Al igual que la directiva #byte para mapear un registro en la memoria RAM y utilizarlo como

una variable más, con la directiva #bit identificador registro.bit podemos mapear un bit en

concreto de un registro. Aunque ya vimos que CCS incluye funciones para el manejo de bits,

está opción es muy cómoda de utilizar y hará que nuestro código sea más portable para utilizar

en otros compiladores.

#int_EXT void EXT_isr(void) { //pon aquí el código de la interruptor }

Esta parte la crea el asistente e incluye la función de interrupción, donde tendremos que incluir el

código que queremos que se ejecute, cuando se active la interrupción externa por la patilla RB0.

En nuestro caso lo que hace es comprobar si el interruptor de desactivación está abierto, si es así,

activará la alarma conectada a RB7. Y permanecerá en un bucle infinito hasta que la

desactivemos por medio del interruptor.

Si el interruptor está cerrado y pulsamos el sensor de alarma la interrupción se producirá

igualmente, pero al no cumplirse la condición del if no ejecutará ninguna sentencia.

Page 16: 11 Ejercicios Micros en C V2

16

Dentro del bloque principal main, tenemos las sentencias que habilitan las interrupciones

globales y la particular a RB0.

enable_interrupts(GLOBAL); globalenable_interrupts(INT_EXT);

Nota: Por defecto la activación de la interrupción se produce en el flanco de subida, si queremos

que sea en el flanco descendente podemos hacerlo por medio de la función:

ext_int_edge(flanco)

Ejemplo:

ext_int_edge(L_TO_H); --> flanco de subida. ext_int_edge(H_TO_L); --> flanco de bajada.

Si no queremos utilizar las funciones de CCS. Podemos modificar directamente el bit INTEDG

Ejemplo:

#bit INTEDG=0x81.6 INTEDG=1; //flanco ascendente INTEDG=0; //flanco descendente

Para ver como realmente CCS maneja estos registros lo podemos ver si ejecutamos el programa

paso a paso en Proteus, si paramos la simulación en el bucle while de la función principal

obtendremos lo que se muestra en la figura de abajo. Donde se puede ver el estado de los bits de

configuración:

• INTEDG=1 (Flanco de activación ascendente, el que tiene por defecto)

• GIE= 1 (Permitidas las interrupciones globales)

• INTE=1 (Permiso de activación interrupción RB0/INT)

Page 17: 11 Ejercicios Micros en C V2

17

• INTF=0 (Señalización de interrupción)

Si ponemos un Breakpoint en el if que hay dentro de la rutina de interrupción y pulsamos el

sensor de alarma para activar la interrupción los valores de los bits serán estos:

• INTED=1 (Flanco de activación ascendente, el que tiene por defecto)

• GIE= 0 (No permite otra interrupción mientras no se salga de esta)

• INTE=1 (Permiso de activación interrupción RB0/INT, da igual que este a 1 por que

la global está a 0)

• INTF= 1(Señalización de interrupción)

Al salir de la interrupción los bits de configuración vuelven a quedar como al principio, a la

espera de que se vuelva a producir otra interrupción.

El bit de señalización INTF no lo hemos usado en este ejemplo, pero en cualquier otro ejemplo

podremos leer su valor y utilizarlo para lo que queramos.

El código del ejemplo se muestra a continuación:

/*--------------------------------------------------------*\ | Ejemplo uso de interrupción externa por RB0 | | | | InterrupcionRB0.c | \*--------------------------------------------------------*/ #include <16F877.h> #FUSES NOWDT //No utilizar el timer Watch Dog #FUSES XT //Oscilador XT <= 4mhz #FUSES NOPUT //No timer en la alimentación #FUSES NOPROTECT //codigo no protegido #bit RB0=0x06.0 //Identificadores de bits #bit RB1=0x06.1 #bit RB7=0x06.7 #use delay(clock=4000000)//Frecuencia de reloj #int_EXT void EXT_isr(void){ if (RB1) //Si el interruptor de desactivación está abierto { RB7=1; // activa la alarma conectada en RB7 while(RB1); //mientras el RB1=1 bucle infinito RB7=0; //cuando RB1=0, se desacactiva la alarma } } void main(){ set_tris_b(0b01111111); //configura la puerta B RB7=0; //inicializo RB7 enable_interrupts(GLOBAL); //habilito interrupción global enable_interrupts(INT_EXT); //habilito interrupción externa ext_int_edge( H_TO_L );//habilito el flanco interrupción de H a l while(true); //Bucle infinito hasta interrupción }

Page 18: 11 Ejercicios Micros en C V2

18

6. Uso de la Interrupción por cambio de estado en RB4-RB7

Vamos a ver un ejemplo del uso de la interrupción por cambio del estado lógico en alguna de las

4 líneas de más peso de la puerta B (RB7-RB4) del PIC. Para ello dichas líneas tienen que estar

previamente configuradas como entradas. Este recurso hardware es muy utilizado para el control

de teclados.

El registro del PIC encargado de su configuración es INTCON:

INTCON

GIE PEIE T0IE INTE RBIE T0IF INTF RBIF

Bit 7 Bit 0

Como siempre debemos habilitar la interrupción global y la particular:

GIE: Permiso global de interrupción.

1 – Permite todas las interrupciones cuyos bits particulares de permiso lo permitan.

0 – Prohíbe todas las interrupciones

RBIE: Permiso de interrupción por cambio de estado en RB7:RB4

1 – Permite la interrupción

0 –Prohíbe está interrupción

RBIF: Señalización ó flag por cambio de estado de las patillas RB7:RB4.

1 – indica que la interrupción se ha producido.

0 – indica que no se ha activado.

Como de costumbre para ver como CCS gestiona este recurso creamos el siguiente esquema en

Proteus:

Page 19: 11 Ejercicios Micros en C V2

19

Se trata de la gestión de un mini teclado formado solo por cuatro teclas representadas por los

pulsadores del 1 al 4. El valor en decimal de la tecla pulsada se mostrará en un display de cátodo común.

En esta ocasión se utiliza el PIC 16F877, a nivel de código lo único que hay que cambiar con

respecto a si utilizáramos el 16f84A es el include donde están definidos los registros del PIC y

los fusibles correspondientes si los hemos incluido, pero en este caso hay una razón más para no

utilizar el 16f84 que se explicará en el comentario del programa.

Si creamos el programa con el asistente, además de seleccionar el PIC correspondiente, tenemos

que marcar en Interrupciones la opción de permitir la interrupción particular por cambio en B4-

B7, según se muestra en la figura de abajo:

El código del programa generado por el asistente tendremos que dejarlo de la siguiente manera:

/*--------------------------------------------------------*\ | Recursos del PIC. Uso de la interrupción por cambio | | en las patillas RB4-RB7 del PIC. | | InterrupcionRB4_7.c | \*--------------------------------------------------------*/ #include <16F877.h> //Configuración de los fusibles. #FUSES NOWDT, XT, NOPUT, NOPROTECT, NOBROWNOUT, NOLVP, NOCPD, NOWRT, NODEBUG #use delay(clock=4000000) //Frecuencia de reloj 4MHz #byte puerto_B = 0x06 // Identificador para el puerto B. #byte puerto_D = 0x08 // Identificador para el puerto C. #int_RB void RB_isr(void) //función de interrupción { switch(puerto_B) { case 239: puerto_D = 0x06; break; case 223: puerto_D = 0x5B;

Page 20: 11 Ejercicios Micros en C V2

20

break; case 191: puerto_D = 0x4F; break; case 127: puerto_D = 0x66; break; default: break; } } void main() { set_tris_b(0xFF); //Puerto B como entrada set_tris_d(0x00); //Puerto D como salida. enable_interrupts(INT_RB); //habilita interrupción particular enable_interrupts(GLOBAL); //habilita interrupción global puerto_D=0x00; //inicializo puerto D while(True) { //código principal } }

Nota: el asistente nos incluye algunas líneas más, sobre opciones de configuración de recursos

que no estamos utilizando y que están deshabilitadas, puedes dejarlas ó quitarlas, pero es una

buena costumbre si utilizar el Wizard el quitar el código que no sea necesario para el

funcionamiento de nuestro programa.

Comentario: Dentro de la función principal configuramos el puerto B como entradas digitales,

(recordar que es un requisito que RB4-RB7 estén configuradas como entradas para la utilización

de esta interrupción) y el puerto D como salida ya que es donde conectaremos el display de siete

segmentos (como es de cátodo común, sus entradas serán activas con nivel alto).

Habilitamos la interrupción global y particular, por medio de las siguientes instrucciones:

enable_interrupts(INT_RB); enable_interrupts(GLOBAL);

e inicializamos el puerto D con el valor 0.

Luego viene un bucle while infinito, la ejecución del programa permanecerá aquí, hasta que se

produzca la interrupción.

Dentro de la función de interrupción chequeamos el valor en decimal del puerto B por medio de

una sentencia de control switch y según sea la tecla pulsada mostramos su valor en el display.

Los cuatro bits menos significativos de la puerta B en el circuito están fijados a uno

permanentemente, por ejemplo si se pulsa el pulsador 1 sonemos RB4=0 y el valor binario a la

entrada de la puerta B será:

RB7 RB6 RB5 RB4 RB3 RB2 RB1 RB0

1 1 1 0 1 1 1 1

Page 21: 11 Ejercicios Micros en C V2

21

Que en decimal tendrá un valor de 239, de la misma forma determinamos el valor para las otras

posibles entradas.

En la tabla de abajo se muestran los valores a utilizar para configurar el display de siete

segmentos en cátodo común.

Display Dígito G F E D C B A Valor Hex.

Valor Dec.

0 0 1 1 1 1 1 1 3F 63

1 0 0 0 0 1 1 0 06 6

2 1 0 1 1 0 1 1 5B 91

3 1 0 0 1 1 1 1 4F 79

4 1 1 0 0 1 1 0 66 102

5 1 1 0 1 1 0 1 6D 109

6 1 1 1 1 1 0 1 7D 125

7 0 0 0 0 1 1 1 07 7

8 1 1 1 1 1 1 1 7F 127

9 1 1 0 1 1 1 1 6F 111

Para ver como CCS gestiona está interrupción, simulamos el circuito en Proteus cargando el

archivo .cof en el PIC, para que nos permita la simulación paso a paso, al igual que hicimos con

el ejemplo de la interrupción externa por RB0/INT.

Para parar el programa dentro de la función de interrupción podemos poner un breakpoint dentro de cualquiera de las sentencias case.

El valor del registro INTCON según la secuencia del programa será la siguiente:

Antes de producirse la interrupción:

• GIE= 1 (Permitidas las interrupciones globales)

• RBIE=1 (Permiso de interrupción por cambio de estado en RB7:RB4)

• RBIF=0 (Señalización por cambio de estado de las patillas RB7:RB4 a nivel bajo)

Al producirse la interrupción:

• GIE= 0 (No Permitidas las interrupciones globales)

• RBIE=1 (Permiso de interrupción por cambio de estado en RB7:RB4, me da igual

como este porque la global está deshabilitada)

• RBIF=1 (Señalización por cambio de estado de las patillas RB7:RB4 a nivel alto)

Al salir de la interrupción:

• GIE= 1 (Permitidas las interrupciones globales)

• RBIE=1 (Permiso de interrupción por cambio de estado en RB7:RB4)

Page 22: 11 Ejercicios Micros en C V2

22

• RBIF=0 (Señalización por cambio de estado de las patillas RB7:RB4 a nivel bajo)

Hasta aquí CCS gestiona la interrupción por cambio de estado en RB4:RB7 de forma similar a

como lo hacía con la interrupción externa RB0/INT, es decir, gestionando la habilitación del bit

GIE y controlando el valor del flag RBIF, según se esté dentro o fuera de la función de

interrupción, todo ello de forma transparente para el programador.

Pero en este caso hay un paso que los creadores del compilador se han pasado por alto y obliga al

programador a tener en cuenta cuando se trabaje con este tipo de interrupción y es la siguiente:

Si nos vamos al datasheet del PIC 16f877, en lo que respecta al control de esta interrupción

podemos ver lo siguiente:

"Four of the PORTB pins, RB7:RB4, have an interrupton-

change feature. Only pins configured as inputs can

cause this interrupt to occur (i.e., any RB7:RB4 pin

configured as an output is excluded from the interrup ton-

change comparison). The input pins (of RB7:RB4)

are compared with the old value latched on the last read of PORTB. The “mismatch” outputs of RB7:RB4

are OR’ed together to generate the RB Port Change

Interrupt with flag bit RBIF (INTCON<0>)."

De aquí se deduce, que es necesario una lectura (ó escritura) del puerto B cuando se produzca

esta interrupción, en el ejemplo que hemos hecho no hay problema ya que nada más entrar en la

función de interrupción leemos el estado del puerto por medio de la sentencia switch (puerto_B).

Pero imaginaros el ejemplo de la alarma que hicimos por activación de la interrupción RB0/INT,

cuando la alarma está desactivada y pulsamos el sensor de alarma, la secuencia del programa

entra en la función de interrupción pero no hace nada dentro de ella, si intentáramos implementar

ese mismo circuito pero con cuatro sensores de alarma y utilizando la interrupción por cambio de

estado en RB4:RB7, si no incluimos una lectura del puerto dentro de la función de interrupción,

aunque no nos sirva para nada, el programa se vuelve loco y no funciona bien este recurso del

PIC.

Otro problema que se encuentra, es al realizar el ejemplo con el PIC16f84A. Al simular el

circuito con Proteus aparece el siguiente error:

violation access error in module <UNKNOW>

La versión de compilador que se utilizó para el ejemplo fue la 4.038 y la de Proteus la 7.1 SP2.

La causa del error se debe a un bug del compilador que se producía al incluir en el código esta

interrupción y utilizar el archivo .hex ó .cof resultante en Proteus. Si en vez del PIC 16f84A se

utiliza otro, como el 16F877, todo funciona correctamente. Por lo que se sabe, este bug está ya

corregido en versiones posteriores del compilador. Recordar que el error se produce al simular en

Proteus, no al compilar el código.

Page 23: 11 Ejercicios Micros en C V2

23

7. Uso del TMR0 como contador

Vamos a ver en este ejemplo como utilizar el TIMER 0 con una frecuencia de reloj externa al

microcontrolador, la señal externa la aplicaremos, como no, a la patilla RA4/TOCKI del PIC,

dicha señal la utilizaremos para generar una interrupción a través del TIMER0 cada segundo, en

la función de interrupción implementaremos el código necesario para hacer parpadear un Led en

la patilla RB7 del PIC.

La frecuencia de la señal de reloj externa que utilizaremos será de 400 Hz y el Timer 0 lo

configuraremos para que empiece a contar en el flanco de subida de la señal de reloj, el circuito

que tenemos que implementar será el siguiente:

RA0/AN02

RA1/AN13

RA2/AN2/VREF-4

RA4/T0CKI6

RA5/AN4/SS7

RE0/AN5/RD8

RE1/AN6/WR9

RE2/AN7/CS10

OSC1/CLKIN13

OSC2/CLKOUT14

RC1/T1OSI/CCP216

RC2/CCP117

RC3/SCK/SCL18

RD0/PSP019

RD1/PSP120

RB7/PGD40

RB6/PGC39

RB538

RB437

RB3/PGM36

RB235

RB134

RB0/INT33

RD7/PSP730

RD6/PSP629

RD5/PSP528

RD4/PSP427

RD3/PSP322

RD2/PSP221

RC7/RX/DT26

RC6/TX/CK25

RC5/SDO24

RC4/SDI/SDA23

RA3/AN3/VREF+5

RC0/T1OSO/T1CKI15

MCLR/Vpp/THV1

U1

PIC16F877

X1CRYSTAL

R1

10k

C1

15p

C2

15p

D1LED-RED

U1(RA4/T0CKI)

R2

300

A

B

C

D

Bien, primeramente vamos a ver como se tiene que configurar el registro OPTION para que el

TMR0 trabaje de esta forma:

Primeramente el bit TOCS tenemos que ponerlo a 1, recordar que:

• Si TOCS=1, el TMR0 actúa como contador.

• Si TOCS=0, el TMR0 actúa como temporizador

Page 24: 11 Ejercicios Micros en C V2

24

Luego vamos hacer nuestros cálculos, teniendo en cuenta que queremos tener una interrupción

cada segundo, si os fijáis en la formula de abajo es parecida a la que vimos en el ejemplo

anterior, a excepción de que la frecuencia de reloj externa no está multiplicada por 4. Al igual

que antes elegimos un Prescaler, en este ejemplo 4 (podíamos a ver elegido otro cualquiera que

estuviera en la tabla) y calculamos el valor con el que tenemos que inicializar el TMR0 para que

se produzca su desbordamiento cada segundo.

Si despejamos el valor que tenemos que cargar en el TMR0 nos sale un valor de 156 que en

hexadecimal es de 0x9C.

La función en C para configurar el TMR0 lógicamente es la misma que en el ejemplo anterior

pero con parámetros diferentes:

setup_timer_0(RTCC_EXT_L_TO_H|RTCC_DIV_4);

Las palabras utilizadas para declarar los parámetros definen ya su significado. Pero para ver

directamente lo que hace la función, podemos ejecutar el programa paso a paso, o poner un

breakpoint en la línea siguiente a la instrucción de arriba, abrir la ventana de visualización de los

registros internos del PIC que nos ofrece el entorno de Proteus y ver cómo queda el registro

OPTION después de ejecutar la sentencia:

Como vemos en los registros del PIC, el bit 5 del registro OPTION está a 1 (reloj externo) y los

bits PS2:PS0 tienen el valor 001 que corresponde a un preescaler de 1:4 que es el que habíamos

elegido.

Page 25: 11 Ejercicios Micros en C V2

25

La señal de reloj externa la podemos simular por medio de Proteus, si seleccionamos Generator

Mode -> DCLOCK nos saldrá una ventana que configuraremos según se muestra en la figura de

abajo:

Si analizamos la tensión en el ánodo del Led mediante una gráfica, observamos que la señal

cuadrada generada a partir de la interrupción en el pic tiene entre el flanco de subida y el flanco

de bajada una diferencia en el tiempo de 1 s que es el valor que queríamos.

Page 26: 11 Ejercicios Micros en C V2

26

Si utilizáis el asistente para generar el esqueleto de la aplicación, tal como hicimos en el ejemplo

anterior el timer hay que configurarlo de la siguiente forma:

Veis que sale un Overflow de 2.5 s, esto es lo que saldría en la formula si dejamos al TMR0 que

empiece a contar desde 0.

El código del ejemplo será el siguiente:

#include <16F877A.h> #fuses XT,NOWDT #use delay(clock=4000000) #bit RB7=0x6.7 //definimos RB7 como el pin 7 de la puerta B #int_TIMER0 //la siguiente función tiene que ser la de interrupción del TMR0 void TIMER0_isr(void) //function interrupción TMR0 { if (RB7==0)

Page 27: 11 Ejercicios Micros en C V2

27

{ RB7=1; set_TIMER0(0x9C); //inicializa el timer0 } else { RB7=0; set_TIMER0(0x9C); //inicializa el timer0 } } void main() { setup_timer_0(RTCC_EXT_L_TO_H|RTCC_DIV_4); enable_interrupts(INT_TIMER0); enable_interrupts(GLOBAL); set_tris_b(0b01111111); //configura RB7 como salida el resto como entrada RB7=0; //inicializo el bit RB7 a 0; set_TIMER0(0x9C); //inicializa el timer0 //el bucle infinito es necesario ya que si el micro entra en sleep //desactiva la interrupción del TMR0 while(true); }

Como veis no he utilizado el PIC 16F84 sino el 16F877A pero es exactamente igual lo único que

hay que cambiar es la directiva #include<16f84.h> por #include <16f877A.h>. Microchip hace

coincidir por compatibilidad las direcciones de memoria de los registros de los dispositivos de

inferior categoría, de esta manera con solo cambiar una línea de código podemos emigrar nuestra

aplicación a otro PIC de mayor capacidad.

Bueno a lo mejor alguno piensa que para hacer parpadear un Led no hace falta tanta historia.

Pero no se trata del que se hace, sino del como se hace, el contenido de la función de

interrupción se puede cambiar por otro cualquiera según lo que queramos hacer en cada

programa.

Una modificación que se podría hacer sería obtener la señal de reloj externa por medio de un

circuito digital para poder implementar, si se quiere, el circuito en una placa.

Page 28: 11 Ejercicios Micros en C V2

28

8. Uso del TMR0 como temporizador

En esta práctica vamos a ver cómo utilizar el Timer0 con el compilador CCS.

Pero antes vamos a ver un poco de teoría:

El Timer0 es un temporizador/contador ascendente de 8 bits, cuando trabaja con el reloj del PIC

se le suele llama temporizador y cundo los pulsos los recibe de una fuente externa a través de la

patilla RA4/TOCKI se le llama contador, pero digamos que es el mismo perro con dos collares

diferentes. Para no liarnos con las patillas y el nombre de los registros voy a mostrar los registros

y patillas implicadas solo en el TMR0 utilizando el PIC16f877, aunque podría ser otro cualquiera

ya que este temporizador viene incorporado en todos los PIC.

RA4/TOCKI: cuando el temporizador trabaje como contador, los pulsos externos los recibirá a

través de esta patilla.

OSC1/CLKIN y OSC2/CLKOUT: son para conectar el oscilador que nos determinará a la

frecuencia que va a trabajar nuestro PIC, los dos tipos de osciladores más usados son el XT

(cristal de cuarzo) y el RC (resistencia y condensador).

Nota: cuando las patillas traen más de una función separadas por la barra del siete quiere decir

que según la configuración de los registros SFR que se muestran más abajo se utilizarán de una

manera o de otra.

OPTION

#RBPU INTED TOCS TOSE PSA PS2 IPS1 PS0

Bit 7 Bit 0

INTCON

GIE PEIE T0IE INTE RBIE T0IF INTF RBIF

Bit 7 Bit 0

Como podemos ver solo están marcados de un color diferente los que están implicados en el

funcionamiento del TMR0, vamos a ver la función que tienen:

TMR0: es un registro del que podemos leer el valor del contador en tiempo real, pero también

podemos escribir en él y alterar el valor de conteo.

OPTION: los bits que están en color verde son los que están implicados en la configuración del

TIMER0:

• PS0, PS1 y PS2: Configuración del preescaler. El preescaler es un divisor de pulsos que

está a la entrada del Timer-contador. El prescaler divide el número de pulsos que le

entran al timer-contador o al Wachtdog. El factor de división es el siguiente (según los

valores de PS2, PS1 y PS0 respectivamente

Page 29: 11 Ejercicios Micros en C V2

29

• PSA: Bit de asignación de prescaler. Si está a "1" el prescaler se asigna a WDT

(Wachtdog), si está a "0" se asigna al TMR0.

• TOSE: Bit de selección del tipo de flanco para el TMR0. A "1" se incrementa TMR0 por

flanco descendente de RA4, a "0" se incrementa TMR0 por flanco ascendente de RA4.

• TOCS: Selecciona la entrada de reloj de TMR0. A "1" la entrada de reloj de TMR0 es por

flanco de la patilla RA4, a "0" la entrada de reloj de TMR0 es por ciclo de reloj interno.

Pero el PIC también permite que se produzca una interrupción por desbordamiento del TMR0.

Cuando se produce una interrupción el programa abandona temporalmente lo que estaba

haciendo para atender la subrutina de interrupción, pero antes guarda en una región especial de la

memoria llamada pila la dirección de la siguiente instrucción de programa, para que cuando

acabe la subrutina de interrupción pueda seguir ejecutando el programa por donde se había

quedado.

El registro que configura todas las interrupciones es el INTCON, como veis está mapeado en los

dos bancos, una cosa muy útil cuando se trabaja en ensamblador para no tener que cambiar de

banco constantemente para su configuración, pero que en C no es necesario saber ya que es el

compilador el que se encarga de gestionar esto de una manera transparente para el programador.

Pues bien vamos a ver lo que significa cada uno de los bits que están implicados en la

interrupción por el TMR0:

• TOIF: solo se puede leer su valor, es un Flag o bandera que se pone a “1” cuando se

produce un desbordamiento del TMR0, (este o no este configurado para producir una

interrupción). Cuando se trabaja en Ensamblador, este bit hay que ponerlo a "0" por

programa, con CCS no tenemos que preocuparnos por ello. (je, je,.. otra cosa que nos

ahorramos)

• TOIE (Habilita la interrupción por desbordamiento de TMR0). Si este bit esta a "1" la

interrupción por desbordamiento de TMR0 es posible.

• GIE (Habilita las interrupciones globalmente). Este bit permite que cualquier interrupción

sea posible. Para poder usar cualquier interrupción hay que habilitarla globalmente e

individualmente.

Bueno como veis hay varias variables que podemos utilizar a nuestro antojo para utilizar este

recurso del PIC, la fórmula que las relaciona es la siguiente:

Page 30: 11 Ejercicios Micros en C V2

30

Y para ver cómo funciona todo esto con el compilador CCS vamos hacer el siguiente ejemplo:

Se trata de usar el Timer0 para generar una interrupción cada 32 ms utilizando el reloj interno del

microcontrolador. La función de interrupción se utilizará para hacer parpadear un Led en la

patilla RB7 del PIC.

Para ello vamos a utilizar en el circuito un cristal de cuarzo de 4 MHz.

Si nos fijamos en la formula anterior tenemos Interrupt_TMR0= 32 ms que es el valor que nos da

el ejemplo, también tenemos F.Oscilador = 4 MHz y nos quedan dos valores para tener resueltas

todas las variables de la ecuación el valor TMR0 que ya hemos dicho que se puede sobrescribir

su valor en cualquier momento durante la ejecución del programa y el Prescaler, pues le damos

el valor que queramos al Prescaler por ejemplo 256 y calculamos en la fórmula el valor que

tenemos que escribir en el registro TMR0 para obtener los 32 ms:

Si despejamos el valor del TMR0 nos sale un valor de 131 que en hexadecimal es 0x83.

Pues bien ya solo queda implementar todo esto en C, para ello en este ejemplo (y de paso vemos

una nueva funcionalidad que nos presenta este entorno de programación de PIC) vamos a utilizar

el Wizard o asistente para generar proyectos. Quiero aclarar primero que este asistente está

todavía en desarrollo por lo que es recomendable revisar el código que genera antes de compilar,

pues casi siempre tendremos que corregir algún error de sintaxis en el código generado, pero

suele ser una gran ayuda para el que empieza a trabajar con este compilador porque te genera un

código que es como una plantilla o esqueleto donde están ya incluidas las funciones para manejar

los recursos del PIC que hemos seleccionado con el asistente, para ello hacemos clic con el ratón

en el icono que pone PIC Wizard (si, si en el sombrero del mago), nos saldrá un cuadro de

dialogo de Windows para que guardemos nuestro proyecto donde queramos, una vez hecho esto

nos aparecerá la siguiente ventana:

Page 31: 11 Ejercicios Micros en C V2

31

En ella podemos ver que a la izquierda podemos seleccionar los diferentes recursos que tienen

los PIC y a la derecha tenemos dos pestañas: en Options configuramos el recurso seleccionado y

en la pestaña Code visualizamos el código que nos genera el asistente para ese recurso, para el

ejemplo que estamos haciendo en General, lo dejamos tal y como se muestra en la figura de

arriba. Si ahora hacemos clic en la pestaña code vemos el código generado por el asistente para

esta opción.

Inserted into .h file: no pertenece al código que se va a compilar, simplemente es una nota

informativa que nos está diciendo que el código que hay mas abajo se va a insertar en el archivo

de cabecera con extensión .h.

#FUSES NOWDT, XT, NOPROTECT: fuses cuya traducción literaria equivaldría a "fusibles" es

una directiva que establece unas configuraciones para el PIC que son necesarias a la hora de

grabar el programa en el, se pueden incluir aquí o configurarlas en el programa de grabación que

utilicemos, se guardan en la memoria de programa y según la versión de PIC que utilicemos

tendremos más o menos opciones, las que aquí aparecen tienen el significado siguiente:

NOWDT --> No se va a utilizar el Wachtdog.

XT --> Se va a utilizar un cristal de cuarzo para la base de tiempos del reloj del PIC.

NOPUT -->( Power Up Timer), cuando se configura como PUT al alimentar el circuito el

Microcontrolador espera un tiempo determinado para que se estabilicen las tensiones antes de

ejecutar el programa.

NOPROTECT --> El código grabado en el PIC no está protegido.

Hay que decir que cuando creamos un proyecto por medio del Wizard, este nos genera tres

archivos, que son los que se muestran en la figura de abajo:

Page 32: 11 Ejercicios Micros en C V2

32

Uno con extensión .c que es donde estará el grueso de nuestra aplicación, otro con extensión .h

que es donde estarán los archivos de cabecera y otro con extensión .pjt que es el proyecto en si y

que nos sirve para abrir y editarlo con el IDE del compilador.

Cuando compilemos y generemos nuestra aplicación aparecerán mas archivos pero estos que

hemos mencionado aquí son los que necesitamos para editar nuestro código fuente.

Sigamos, cuando seleccionemos la pestaña Communications debemos desmarcar la opción de

comunicación con el puerto serie RS232, ya que no la vamos a utilizar.

Configuración de los timmers:

En las opciones de este recurso desmarcamos la casilla Not used del Wachtdog por que no lo

vamos a utilizar, en el Timer 0 en Source seleccionamos Internal ya que vamos a utilizar los

Page 33: 11 Ejercicios Micros en C V2

33

pulsos de reloj del microcontrolador y en Resolution marcamos 256 us, que es el preescaler que

habíamos decidido utilizar. Vemos que las opciones del Timer 1 y el Timer 2 están

deshabilitadas, ya que PIC 16f84A solo tiene el temporizador timer 0.

Bien si ahora hacemos clic sobre la pestaña Code vemos el código generado por el asistente:

No hay gráfico

La primera línea como antes nos dice donde se va a insertar el código, en este caso en el archivo

.C y dentro de la función principal main(). La siguiente línea es una llamada a la función

setup_timer_0( parametro1|parametro2|…), que ya está declarada y definida dentro de las

librerías del compilador y es utilizada para configurar el registro OPTION del PIC, como vemos

acepta parámetros, con el primero RTCC_INTERNAL, le estamos diciendo que vamos a utilizar

el reloj interno, lo que viene después aunque en la figura no se vea muy bien es una barra vertical

“|” y es el símbolo que hay que utilizar para separar los diferentes parámetros que utilicemos, el

segundo parámetro que nos ha puesto es RTCC_DIV_1);); y como veis aparece doble el cierre

del paréntesis y el punto y coma, ¡¡error del asistente!!, lo que realmente tenemos que poner es

esto: RTCC_DIV_256, ya que este segundo argumento le pasa a la función el valor del

preescaler y queremos que sea 256. Bueno esto lo que me ha generado a mí, lo mismo a vosotros

os va bien. Vemos que no podemos corregir el código aquí, lo haremos después en los archivos

fuente que nos genera.

Por último nos queda configurar el registro INTCON, para ello seleccionamos Interrups y en

Options marcamos la casilla Timer0 overflow (usando como nombre de la interrupción

TIMER0).

El código generado será el siguiente:

Page 34: 11 Ejercicios Micros en C V2

34

Como vemos la rutina de interrupción va antes de la función main y consta de la directiva del

preprocesador #int_TIMER0, con esta directiva el programa sabe a qué dirección de programa

debe de ir cuando se dispare esta interrupción (podemos tener otras), y de la función de

interrupción propiamente dicha, donde tenemos que incluir nuestro código, veámoslo aquí

debajo de una forma más clara:

#int_TIMER0 Void TIMER0_isr(void) { //Nuestro código de interrupción. }

Dentro de la función main incluye lo siguiente:

enable_interrupts(INT_TIMER0); enable_interrupts(GLOBAL);

La primera instrucción habilita la interrupción particular del Timer 0 por desbordamiento y la

otra habilita las interrupciones globalmente, como dijimos al principio no hay que preocuparse

por resetear el flag de interrupción TOIF, ni de desactivar las interrupciones globales mientras se

esté dentro del código de interrupción por si se produce otra simultáneamente, como hay que

hacer cuando se trabaja en ensamblador, estas funciones lo hacen también pero de una manera

transparente al programador, por eso es un lenguaje de alto nivel.

Después de terminar con la configuración de los recursos pulsamos el botón de aceptar y el

asistente nos creará los archivos correspondientes:

Archivo de cabecera:

Archivo .C principal:

Page 35: 11 Ejercicios Micros en C V2

35

Pues bien este es el esqueleto de nuestra aplicación y sin escribir una línea de código. Ahora

sobre esta plantilla tenemos que escribir nuestro código para hacer nuestra aplicación funcional.

En el archivo de cabecera incluiremos la línea que está en negrita:

#include <16F877.h> #use delay(clock=4000000) #bit RB7=0x6.7 //definimos RB7 como el pin 7 de la puerta B

Con la directiva #bit le podemos asignar la dirección de memoria del bit de un registro a una

variable que podremos utilizar después para referirnos a ese bit.

En el archivo principal TMR0.C añadiremos también las siguientes líneas marcadas en negrita:

#include "C:\Prácticas PIC C\TMR0\TMR0.h" #int_TIMER0 void TIMER0_isr(void) { if (RB7==0) { RB7=1; set_TIMER0(0x83); //inicializa el timer0 } else { RB7=0; set_TIMER0(0x83); //inicializa el timer0 } } void main() { set_tris_b(0b01111111); //configura RB7 como salida el resto como entrada RB7=0; //inicializo el bit RB7 a 0; setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256); enable_interrupts(INT_TIMER0); enable_interrupts(GLOBAL); set_TIMER0(0x83); //inicializa el timer0 //el bucle infinito es necesario ya que si el micro entra en sleep //desactiva la interrupción del TMR0 while(true); }

Page 36: 11 Ejercicios Micros en C V2

36

Con la directiva #include lo que se hace es incluir un archivo dentro de otro, si queremos tener

todo el código fuente en un mismo archivo lo único que tenemos que hacer es sustituir esta línea

por el contenido del archivo de cabecera, pero es recomendable separar los archivos de cabecera

del resto del programa. C es un lenguaje estructurado y así es como trabaja, cuanto antes nos

acostumbremos a ello mejor.

¡ojo¡ si utilizamos los dos archivos y copiamos el código fuente y lo pegamos en un directorio

diferente tendremos que modificar la ruta actual al archivo de cabecera si no al compilar dará un

error de archivo no encontrado.

El funcionamiento del programa es muy simple, después de inicializar el puerto B inicializamos

el TMR0 por medio de la función set_TIMER0, el valor que recibe como parámetro esta función

es el valor que se escribirá en el TMR0 que es 0x86, valor que habíamos obtenido de la fórmula

anteriormente, como está activada la interrupción por desbordamiento del contador este empieza

a contar a partir de 0x86 ó 131 en decimal hasta 255 que equivale a 11111111 en binario como

es un contador de 8 bits al siguiente pulso de reloj se produce el desbordamiento y el contador de

programa va a la dirección de memoria donde está el vector de interrupción del TMRO y ejecuta

las instrucciones que están dentro de su función. La función de interrupción lo único que hace es

a través de una sentencia de control if–else encender o apagar el led, fijaros que en cada caso se

vuelve a cargar el valor del TMR0 para que el contador vuelva a empezar a contar por 131 y no

por cero, de esta manera la interrupción se producirá cada 32 ms que es lo que queríamos.

Bueno a hora toca construir nuestro circuito virtual y simularlo con Proteus para comprobar que

efectivamente es así.

RA0/AN02

RA1/AN13

RA2/AN2/VREF-4

RA4/T0CKI6

RA5/AN4/SS7

RE0/AN5/RD8

RE1/AN6/WR9

RE2/AN7/CS10

OSC1/CLKIN13

OSC2/CLKOUT14

RC1/T1OSI/CCP216

RC2/CCP117

RC3/SCK/SCL18

RD0/PSP019

RD1/PSP120

RB7/PGD40

RB6/PGC39

RB538

RB437

RB3/PGM36

RB235

RB134

RB0/INT33

RD7/PSP730

RD6/PSP629

RD5/PSP528

RD4/PSP427

RD3/PSP322

RD2/PSP221

RC7/RX/DT26

RC6/TX/CK25

RC5/SDO24

RC4/SDI/SDA23

RA3/AN3/VREF+5

RC0/T1OSO/T1CKI15

MCLR/Vpp/THV1

U1

PIC16F877

X1CRYSTAL

R1

10k

C1

15p

C2

15p

D1LED-RED

R2

300

A

B

C

D

Page 37: 11 Ejercicios Micros en C V2

37

Hemos agregado una sonda de tensión y para poder ver los valores que toma en el tiempo

incluimos una grafica en simulación digital. Si ampliamos la gráfica veremos que cada intervalo

tiene una duración de 32 ms. Como siempre el DSN y el código fuente os lo podéis bajar desde

la sección Descargas –> Apuntes -> Electrónica -> Microcontroladores -> Ejemplos prácticos

programación PIC en C.

Bueno yo creo que mas mascado no puede estar, para aquellos que no conozcan el lenguaje C y

les suene a chino lo que es una función y lo de pasar parámetros a una función. “No problem” en

el curso de teoría se verá detenidamente.

En programa también puede ser:

#include <16F877.h> #use delay(clock=4000000) #bit RB7=0x6.7 //definimos RB7 como el pin 7 de la puerta B #int_TIMER0 void TIMER0_isr(void) { if (RB7==0) { RB7=1; set_TIMER0(0x83); //inicializa el timer0 } else { RB7=0; set_TIMER0(0x83); //inicializa el timer0 } } void main() { set_tris_b(0b01111111); //configura RB7 como salida el resto como entrada RB7=0; //inicializo el bit RB7 a 0; setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256); enable_interrupts(INT_TIMER0); enable_interrupts(GLOBAL);

Page 38: 11 Ejercicios Micros en C V2

38

set_TIMER0(0x83); //inicializa el timer0 //el bucle infinito es necesario ya que si el micro entra en sleep //desactiva la interrupción del TMR0 while(true); }

Page 39: 11 Ejercicios Micros en C V2

39

9. Uso del TMR0 como contador o temporizador

El ejemplo utiliza el TMR0 como contador y temporizador, seleccionado mediante un switche en

la patilla RB6 del PIC. Generar una interrupción cada 32 ms mediante el reloj interno y generar

una interrupción cada 1 seg con la frecuencia de reloj externa de 400 Hz que se aplica la patilla

RA4/TOCKI del PIC; y el Timer 0 se configura para que empiece a contar en el flanco de subida

de la señal de reloj externo. En la función de cada interrupción se implementa el código

necesario para hacer parpadear un LED en la patilla RB7 del PIC.

1. Utilizar el TIMER 0 como temporizador. Utilizar el Timer0 para generar una

interrupción cada 32 ms, mediante el reloj interno del microcontrolador. La función de

interrupción se utilizará para hacer parpadear un LED en la patilla RB7 del PIC. Para ello

se utiliza en el circuito un cristal de cuarzo de 4 MHz.

Para el cálculo se utiliza la siguiente fórmula, la cual tiene un 4, Fciclo máquina = 4

Foscilación:

Se tiene Interrupt_TMR0 = 32 ms que es el valor que da el ejemplo, también se tiene

F.Oscilador = 4 MHz, y queda dos valores para tener resueltas todas las variables de la

ecuación; ValorTMR0 que se puede sobrescribir su valor en cualquier momento durante

la ejecución del programa y el Prescaler, puede darse el valor que se quiera al Prescaler

por ejemplo 256, y se calcula en la fórmula el valor que se debe escribir en el registro

TMR0 para obtener los 32 ms:

Si se despeja el valor del TMR0 sale un valor de 131 que en hexadecimal es 0x83.

La función en C para configurar el TMR0 tiene los siguientes parámetros:

setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256);

2. Utilizar el TIMER 0 como contador. La frecuencia de reloj externa al

microcontrolador, se aplica la patilla RA4/TOCKI del PIC, dicha señal se la utiliza para

generar una interrupción a través del TIMER0 cada segundo, en la función de

interrupción se implementa el código necesario para hacer parpadear un LED en la patilla

RB7 del PIC. La frecuencia de la señal de reloj externa que se utiliza será de 400 Hz y el

Timer 0 se configura para que empiece a contar en el flanco de subida de la señal de

reloj.

Para el cálculo, debe tener en cuenta que se quiere tener una interrupción cada segundo,

en la siguiente fórmula la frecuencia de reloj externa no está multiplicada por 4. Se elige

un Preescaler, en este ejemplo 4, (se puede a ver elegido otro cualquiera que estuviera en

la tabla) y se calcula el valor con el que se tiene que inicializar el TMR0; para que se

produzca su desbordamiento cada segundo.

Page 40: 11 Ejercicios Micros en C V2

40

Si se despeja ValorTMR0, corresponde al valor inicial que se tiene que cargar en el

TMR0, que será el valor 156 que en hexadecimal es de 0x9C.

La función en C para configurar el TMR0 tiene los siguientes parámetros:

setup_timer_0(RTCC_EXT_L_TO_H|RTCC_DIV_4);

NOTA: Si el TMR0 empieza a contar desde 0, se tendrá un Overflow de 2.5 s, esto es lo

que saldría en la fórmula.

El circuito a implementar será el siguiente:

RA0/AN02

RA1/AN13

RA2/AN2/VREF-4

RA4/T0CKI6

RA5/AN4/SS7

RE0/AN5/RD8

RE1/AN6/WR9

RE2/AN7/CS10

OSC1/CLKIN13

OSC2/CLKOUT14

RC1/T1OSI/CCP216

RC2/CCP117

RC3/SCK/SCL18

RD0/PSP019

RD1/PSP120

RB7/PGD40

RB6/PGC39

RB538

RB437

RB3/PGM36

RB235

RB134

RB0/INT33

RD7/PSP730

RD6/PSP629

RD5/PSP528

RD4/PSP427

RD3/PSP322

RD2/PSP221

RC7/RX/DT26

RC6/TX/CK25

RC5/SDO24

RC4/SDI/SDA23

RA3/AN3/VREF+5

RC0/T1OSO/T1CKI15

MCLR/Vpp/THV1

U1

PIC16F877

X1CRYSTAL

R1

10k

C1

15p

C2

15p

D1LED-RED

U1(RA4/T0CKI)

R2

300

R3300

A

B

C

D

El código del ejemplo será el siguiente:

#include <16F877.h> #use delay(clock=4000000) #bit RB6=0x6.6 //definimos RB7 como el pin 6 de la puerta B #bit RB7=0x6.7 //definimos RB7 como el pin 7 de la puerta B #int_TIMER0 void TIMER0_isr(void) { if (RB6) {

Page 41: 11 Ejercicios Micros en C V2

41

setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256); set_TIMER0(0x83); //inicializa el timer0 } else { setup_timer_0(RTCC_EXT_L_TO_H|RTCC_DIV_4); set_TIMER0(0x9C); //inicializa el timer0 } if (RB7==0) RB7=1; else RB7=0; } void main() { set_tris_b(0b01111111); //configura RB7 como salida el resto como entrada RB7=0; //inicializo el bit RB7 a 0; setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256); enable_interrupts(INT_TIMER0); enable_interrupts(GLOBAL); set_TIMER0(0x83); //inicializa el timer0 //el bucle infinito es necesario ya que si el micro entra en sleep //desactiva la interrupción del TMR0 while(true); }

Page 42: 11 Ejercicios Micros en C V2

42

10. Uso del TMR1 como Temporizador

El Timer1 es un temporizador/contador ascendente parecido al TMR0, pero con algunas

peculiaridades que lo hacen muy interesante a la hora de incluir temporizaciones en nuestros

programas.

La primera de ellas, es que se trata de un contador de 16 bits cuyo valor se almacena en dos

registros de 8 bits el TMR1H y el TMR1L, ambos registros se pueden leer y escribir su valor

durante la ejecución del programa.

Cuando el Timer1 está habilitado, el valor de esos registros se incrementan desde 0000h a FFFFh

y una vez que llega a su máximo valor empieza otra vez desde 0 avisándonos de ello por medio

de la bandera TMR1F.

Si está activa la interrupción por desbordamiento del Timer 1 al desbordarse el contador, el

programa entra en la función de tratamiento a la interrupción por desbordamiento del Timer1.

El diagrama de bloques es el siguiente:

Algunas características de este Timer son las siguientes:

• El Timer1 puede funcionar con un oscilador externo y trabajar a una frecuencia distinta a

la del oscilador principal del PIC.

• Al igual que el TMR0 el Timer1 puede operar en dos modos: como temporizador y como

contador. El modo de funcionamiento está determinado por el tipo de reloj seleccionado

(interno -->temporizador, externo -->contador), lo configuramos por medio del bit

TMR1CS del registro TICON. Cuando está en modo contador su valor se incrementa en

cada flanco de subida de la señal de reloj externa.

• El tiempo que se tarda en incrementarse el contador se le suele llamar paso, el paso del

contador depende de la frecuencia del oscilador y del prescaler seleccionado.

• La fórmula para determinar los tiempos del Timer1 cuando es utilizado como temporizador

(Reloj interno) es la siguiente:

Page 43: 11 Ejercicios Micros en C V2

43

• El paso del contador vendrá determinado por:

Paso_Contador= 4/Frecuencia Oscilación.Prescaler

• El Timer1 se puede habilitar o deshabilitar por medio del bit TMR1ON del

registro T1CON.

Un dibujo con los bits de configuración del registro TICON, lo tienes en la figura de abajo:

Vamos a ver un ejemplo sencillo (como siempre) de cómo utilizar el Timer1 como temporizador

usando el reloj interno del micro. El ejemplo consiste simplemente en hacer parpadear un led a

un intervalo de 0.5 segundos, usando el Timer1 con la interrupción por desbordamiento

habilitada.

Para ello tenemos como siempre dos opciones, escribir nosotros todo el código o ayudarnos por

medio del asistente que trae CCS. Cualquiera de las opciones es válida, ya que el código es muy

sencillo.

El que utilice el asistente, deberá configurar primeramente el micro a utilizar y la frecuencia de

reloj. Yo voy a utilizar el PIC16f877 y un cristal de 4MHz (el viejo amigo de batallas

PIC16f84A no dispone de este recurso).

Page 44: 11 Ejercicios Micros en C V2

44

Después en el apartado timers habrá que configurar los parámetros para el Timer1, como vamos

a trabajar con el Timer1 en modo Temporizador, en el tipo de reloj seleccionaremos Internal,

después seleccionaremos una resolución entre las cuatro disponibles, según se muestra en la

figura de abajo.

La resolución es el tiempo que tarda el contador en incrementar su valor, es decir, el paso del

contador. Este valor, como hemos dicho ya, depende del Preescaler seleccionado y de la

frecuencia del reloj principal. Por ejemplo, si seleccionamos la última opción le estamos

Page 45: 11 Ejercicios Micros en C V2

45

diciendo al asistente que queremos un preescaler de 8 y como hemos seleccionado una

frecuencia de 4MHz para el reloj principal, el paso del contador será igual a:

4/Frecuencia Oscilación.Prescaler= 4/4MHz.8= 8u segundos.

El asistente también nos muestra el Overflow, que como hemos dicho también, es el tiempo que

tardará el contador en desbordarse. Suponiendo que carguemos el TMR1 con valor 0, que es

como viene por defecto, si aplicamos la formula obtendremos el valor de 524ms

(Desbordamiento_Timer1= 4/4MHz*8(65536-0)= 524 ms).

Como en vez de 524 ms, queremos obtener 500 ms. Sustituimos ese valor en la fórmula y

despejamos el valor a cargar en el TMR1:

El valor de TMR1 que sale es de 3036 que en hexadecimal es: 0x0BDC

Por último solo nos queda decirle al asistente que queremos utilizar la interrupción por

desbordamiento del Timer 1, según se muestra en la figura de abajo:

El código de nuestro ejemplo quedará de la siguiente forma:

01.#include <16F887.h>//Pic utilizado 02.//Palabra de configuración de los fusibles. 03.#FUSES NOWDT, XT, NOPUT, NOMCLR, NOPROTECT, NOCPD, NOBROWNOUT, NOIESO, NOFCMEN, NOLVP, NODEBUG, NOWRT, BORV40 04.#use delay(clock=4000000)//Frecuencia del reloj principal 05.#bit RB7=0x06.7 //Identificador para el bit RB7 06. 07.#int_TIMER1

Page 46: 11 Ejercicios Micros en C V2

46

08.void TIMER1_isr(void)//Función de interrupción por desbordamiento TMR1 09.{ 10. RB7=~RB7; //Togle RB7 11. set_timer1(0x0BDC);//carga del TMR1 12.} 13. 14. 15. 16.void main() 17.{ 18. set_tris_b(0b01111111); //configura RB7 como salida el resto como entrada 19. RB7=0;//Inicializo RB7 20. setup_timer_1(T1_INTERNAL|T1_DIV_BY_8);//Setup timer: Reloj interno, preescaler= 8 21. enable_interrupts(INT_TIMER1);//Habilito interrupción particular del TIMER1 22. enable_interrupts(GLOBAL);//Habilito interrupciones globales 23. set_timer1(0x0BDC);//Carga del TMR1 24. while(true);//Bucle infinito hasta interrupción 25. 26.}

Siempre hay que limpiar un poco el código que nos genera el asistente, de parámetros y opciones

que están deshabilitadas y que no nos sirven para nada

El circuito a montar en Proteus, será el siguiente:

En el que se ha incluido una gráfica digital para medir el intervalo de tiempo de parpadeo del

diodo LED.

Si hacemos un zoom sobre la gráfica y añadimos un segundo cursor (Ctrl+c), podemos ver que el

intervalo es de 500 ms.

Page 47: 11 Ejercicios Micros en C V2

47

Comentario del programa:

El programa de lo sencillo que es, está más que explicado con los comentarios hechos en el

código fuente. Solo comentar las funciones que incluye CCS para el control del Timer1:

• set_timer1(value); //Función para inicializar los registros TMR1H y TMR1L.

• value=get_timer1; //Función para leer el valor del timer1

• setup_timer_1 (parámetros); //configuración del Timer1 por medio de parámetros. Los

parámetros pueden ser:

o T1_DISABLED: deshabilita el Timer1, esto me permite un ahorro de energía en el

PIC si no utilizo este recurso.

o T1_INTERNAL: fuente de reloj, el principal. Modo temporizador.

o T1_EXTERNAL: fuente de reloj externa. El timer funcionara como contador y como

condición para empezar el contaje debe de producirse un flanco de bajada en la señal

de reloj, mira la figura de abajo:

Este pequeño detalle que parece poco importante, no lo es en el caso de que queramos contar de

manera precisa los pulsos de entrada.

• T1_EXTERNAL_SYNC: con esto le digo al compilador que la señal de reloj externa se

sincronice con la señal de reloj principal. Como se ve en el diagrama de bloques la

Page 48: 11 Ejercicios Micros en C V2

48

sincronización se produce después del preescaler, por lo que hasta después del preescaler la

señal sigue siendo asíncrona. Hay que tener en cuenta que cuando tenemos definido este

parámetro si el PIC entra en modo SLEEP el TIMER1 no se incrementará aunque este

presente la señal de reloj externa.

• T1_CLK_OUT: con este parámetro pongo a 1 el bit T1OSCEN, para habilitar el oscilador

del Timer1. Cuando tengo seleccionada la fuente de reloj externa y este parámetro a la vez,

me permite independizar el timer1 del oscilador principal, por lo que el timer1 seguirá

contando aunque el PIC entre en modo SLEEP. La entrada de la señal de reloj será por la

patilla RC0/T1OSO/TICK1 del PIC. Cuando este parámetro no está definido T1OSCEN

toma el valor por defecto que es 0, en este caso y siempre y cuando este definido el

parámetro T1_EXTERNAL la entrada de la señal de reloj externa será por

RC1/TlOSI/CCP2. Este modo se utiliza para poder producir un reinicio del Timer1 a partir

de cualquiera de los módulos CCP1 o CCP2 del PIC.

• T1_DIV_BY_1, T1_DIV_BY_2, T1_DIV_BY_4, T1_DIV_BY_8: constantes para

seleccionar el preescaler que queremos seleccionar.

Los parámetros seleccionados se colocan juntos separados por el símbolo |.

Ejemplos:

setup_timer_1 ( T1_DISABLED );

setup_timer_1 ( T1_INTERNAL | T1_DIV_BY_4 );

setup_timer_1 ( T1_INTERNAL | T1_DIV_BY_8 );

Fuentes de información:

• Data Sheet PIC16F87X • Manual CCS compiler

En el próximo artículo (sobre CCS), veremos cómo utilizar el timer1 como contador, utilizando

un reloj externo de 32.768Hz de frecuencia, y veremos si la precisión obtenida es suficiente para

implementar un reloj en tiempo real (RTC) de Horas, minutos y segundos.

El DSN de Proteus lo tienes aquí #include <16F887.h>//Pic utilizado //Palabra de configuración de los fusibles. #FUSES NOWDT, XT, NOPUT, NOMCLR, NOPROTECT, NOCPD, NOBROWNOUT, NOIESO, NOFCMEN, NOLVP, NODEBUG, NOWRT, BORV40 #use delay(clock=4000000)//Frecuencia del reloj principal #bit RB7=0x06.7 //Identificador para el bit RB7 #int_TIMER1 void TIMER1_isr(void)//Función de interrupción por desbordamiento TMR1 { RB7=~RB7; //Togle RB7 set_timer1(0x0BDC);//carga del TMR1 } void main() { set_tris_b(0b01111111); //configura RB7 como salida el resto como entrada RB7=0;//Inicializo RB7 setup_timer_1(T1_INTERNAL|T1_DIV_BY_8);//Setup timer: Reloj interno, preescaler= 8

Page 49: 11 Ejercicios Micros en C V2

49

enable_interrupts(INT_TIMER1);//Habilito interrupción particular del TIMER1 enable_interrupts(GLOBAL);//Habilito interrupciones globales set_timer1(0x0BDC);//Carga del TMR1 while(true);//Bucle infinito hasta interrupción }

Page 50: 11 Ejercicios Micros en C V2

50

11. Uso de la memoria EEPROM interna

CCS implementa muchas funciones para trabajar con las memorias EEPROM, algunas de ellas

son:

• value = read_eeprom (address): función básica para leer el valor de la EEPROM

interna del PIC. Devuelve un valor entero (int8) de un byte. "address" puede ser un entero

de 8 ó 16 bit. Dependiendo del PIC que utilicemos dispondremos de más ó menos

memoria EEPROM, por ejemplo el PIC 16f84A dispone de 64 bytes y los pic16F87X

tienen 256 bytes que se direccionan del 0 a 255.

• write_eeprom (address, value): esta función escribe un dato (entero de 8 bit) en la

dirección especificada en address en la memoria interna del PIC. Al igual que

read_eeprom address puede ser un entero de 8 ó 16 bit.

Algunos dispositivos permiten leer y escribir datos en la memoria de programa en tiempo de

ejecución, para los dispositivos que soportan esta funcionalidad CCS, nos proporciona las

siguientes funciones:

• value = read_program_eeprom (address): esta función lee un dato de la memoria de

programa del PIC y devuelve el valor leído como un entero de 16 bits. Adrress es un

entero de 16 ó 32 bits que depende del dispositivo empleado.

• write_program_eeprom (address, data): función homologa a la anterior pero que nos

permite escribir datos en la memoria de programa. data tiene que ser un entero de 16 bits.

CCS también incorpora funciones para leer y escribir en memorias EEPROM externas:

read_external_memory(address, dataptr, count ) write_program_memory( address, dataptr, count) Como se ve CCS nos aporta una serie de funciones para trabajar fácilmente con este tipo de

memorias, estas que he puesto aquí son algunas de ellas, pero todavía hay más.

Algunas consideraciones a tener en cuenta sobre las memorias EEPROM es que son rápidas en el

proceso de lectura, pero pueden tardar varios ms en realizar un proceso de escritura. Otro factor

a tener en cuenta es que se pueden hacer operaciones de lectura sobre el valor de sus registros el

número de veces que se quiera, pero soportan un número limitado de ciclos de escritura /

borrado. Ese número según Microchip es de aproximadamente un millón.

La memoria EEPROM de datos no está mapeada en la zona de la memoria de datos donde se

ubican los registros SFR y GPR, si programáramos en Ensamblador deberíamos realizar una

serie de pasos para su lectura y escritura que aunque no difíciles resultan al menos laboriosos,

CCS nos permite abstraernos por completo del proceso de lectura y escritura, lo único que

tenemos que saber es las funciones que tenemos que aplicar y los parámetros y valores que

devuelven dichas funciones.

Como ejemplo del uso de este tipo de memorias vamos a utilizar las funciones básicas para leer y

escribir datos en la memoria interna del PIC: read_eeprom (address) y write_eeprom (address,

value).

Page 51: 11 Ejercicios Micros en C V2

51

El ejemplo es un contador del tipo "su turno" que solemos encontrar en algunos establecimientos

como carnicerías y pescaderías. Como funcionalidades mínimas se ha puesto que sea capaz de

contar del 0 al 99.

Y por supuesto que si se va la corriente guarde en memoria el último valor.

El esquema del circuito será el siguiente:

Cuando queremos utilizar más de un display y minimizar el número de patillas en el PIC para su

control hay varias formas de hacerlo, a continuación se muestran:

1. Una de ellas (la que se ha utilizado en este ejemplo) es utilizar un decodificador BCD a 7

segmentos como el 7447.

2. Otra forma es multiplexar las líneas de datos, es decir en cada instante solo habrá un display

activo pero el cambio de uno a otro será tan rápido que para el ojo humano parecerá que los

dos están activos a la vez, este sistema es bueno porque nos ahorramos los decodificadores,

pero si utilizamos más de cuatro display, notaremos un parpadeo molesto.

El código del programa será el siguiente:

01./*-----------------------------------------------------------*\ 02.| Uso de la memoria EEPROM del PIC | 03.| | 04.| | 05.\*-----------------------------------------------------------*/ 06. 07.#include <16F877.h> 08.//Configuración de los fusibles. 09.#FUSES NOWDT, XT, NOPUT, NOPROTECT, NOBROWNOUT, NOLVP, NOCPD, NOWRT, NODEBUG 10.#use delay(clock=4000000) //Frecuencia de reloj 4MHz 11.#byte puerto_D = 0x08 // Identificador para el puerto C. 12. 13. #int_EXT 14. 15. void EXT_isr( void )

Page 52: 11 Ejercicios Micros en C V2

52

16. { 17. 18. if ((read_eeprom(0)==0x99)||(read_eeprom(0)==0xFF)) 19. { 20. write_eeprom(0,0); 21. puerto_D=read_eeprom(0); 22. } 23. else if ((read_eeprom(0) & 0x0F)<0x09) 24. { 25. write_eeprom(0,(read_eeprom(0))+1); 26. puerto_D=read_eeprom(0); 27. } 28. else if ((read_eeprom(0) & 0x0F)>=0x09) 29. { 30. write_eeprom(0,(read_eeprom(0))+7); 31. puerto_D=read_eeprom(0); 32. } 33. 34. } 35. 36. void main() 37. { 38. set_tris_b(0xFF); //Puerto B como entrada 39. set_tris_d(0x00);//Puerto D como salida 40. enable_interrupts(GLOBAL); // Se habilita la interrupción global 41. enable_interrupts(INT_EXT); // Se habilita la interrupción externa 42. puerto_D =0xFF; //inicializo puerto D 43. //write_eeprom(0,0xFF);Resetear EEPROM 44. while(true) 45. { 46. //Resto del programa 47. 48. } 49. }

Comentario del programa: El ejemplo básicamente es un contador ascendente de 0 a 99 que incrementa su valor cada vez

que pulsamos el pulsador "su turno" para evitar que el contador se reinicie y empiece a contar

desde cero cada vez que se vaya la corriente, el valor actual del contador se almacenará en la

memoria EEPROM del PIC en vez de en la memoria RAM. Como solo queremos guardar un

valor que estará comprendido entre 0 y 99, solo utilizaremos el primer byte de la memoria

EEPROM. Para detectar cuando se pulsa el pulsador, se utiliza la interrupción externa en la

patilla RB0/INT.

Dentro de la función de interrupción nos encontramos con tres sentencias condicionales:

if ((read_eeprom(0)==0x99)||(read_eeprom(0)==0xFF)) { write_eeprom(0,0);//escribo el valor 0 en la dirección 0 de la memoria EEPROM puerto_D=read_eeprom(0);//asigno al puerto D el valor devuelto por la función de lectura de la EEPROM }

La primera vez que se ejecute el programa el valor de la EEPROM es 0xFF (viene así de fábrica)

por lo que tendremos que sobre escribir su valor a 0 para iniciar el contaje, después de esto esta

condición solo se cumplirá cuando el contador llegue a 99.

else if ((read_eeprom(0) & 0x0F)<0x09) { write_eeprom(0,(read_eeprom(0))+1); puerto_D=read_eeprom(0);

Page 53: 11 Ejercicios Micros en C V2

53

}

Cada "nibble" del puerto D está conectado a un decodificador por lo que tenemos RD0-RD3 al

decodificador U2 y su salida conectado al display de las unidades a través del bus de datos (hay

que hacer un zoom sobre el esquema en Proteus para ver las conexiones) y RD4-RD7 al

decodificador U3, que está conectado al display de las decenas.

Pues bien, si tenemos 4 bits por "nibble" tendremos 16 combinaciones posibles, incrementamos

con 1 el valor de la EEPROM mientras el valor del "nibble" de menor peso sea < 9. Fijaros como

la función de escritura en la EEPROM nos permite poner como parámetro la función de lectura

por lo que no es necesario declarar una variable local para hacer de intercambio.

Una vez incrementado el valor de la EEPROM, hacemos una nueva lectura y asignamos su valor

al puerto D.

else if ((read_eeprom(0) & 0x0F)>=0x09) { write_eeprom(0,(read_eeprom(0))+7); puerto_D=read_eeprom(0); }

Los valores del "nibble" de menor peso correspondientes al rango 10-15 los saltamos sumando 7

en vez de uno. El nibble de más peso de la lectura de la EEPROM no me interesa ya que el

primer if ya se encarga de ponerlo a cero cuando el valor llegue a 0x99. Para discriminar el

"nibble" de más peso de la EEPROM lo enmascaramos antes de hacer la comparación

Podemos ver los registros de la EEPROM interna del PIC, si en Proteus seleccionamos Debug--

>PIC CPU EPROM Memory.

Page 54: 11 Ejercicios Micros en C V2

54

Proteus simula bastante bien el uso de la memoria EEPROM del PIC, si paramos la simulación y

volvemos a iniciarla vemos como los valores de la EEPROM se mantienen.

El código del programa lo tienes aquí

/****************************************************************************** * * * DESCRIPCIÓN: Uso de la memoria EEPROM del PIC * * EEPROM.c * * * ******************************************************************************/ #include <16F877.h> //Configuración de los fusibles. #FUSES NOWDT, XT, NOPUT, NOPROTECT, NOBROWNOUT, NOLVP, NOCPD, NOWRT, NODEBUG #use delay(clock=4000000) //Frecuencia de reloj 4MHz #byte puerto_D = 0x08 // Identificador para el puerto C. #int_EXT void EXT_isr( void ) { if ((read_eeprom(0)==0x99)||(read_eeprom(0)==0xFF)) { write_eeprom(0,0); puerto_D=read_eeprom(0); } else if ((read_eeprom(0) & 0x0F)<0x09) { write_eeprom(0,(read_eeprom(0))+1);

Page 55: 11 Ejercicios Micros en C V2

55

puerto_D=read_eeprom(0); } else if ((read_eeprom(0) & 0x0F)>=0x09) { write_eeprom(0,(read_eeprom(0))+7); puerto_D=read_eeprom(0); } } void main() { set_tris_b(0xFF); //Puerto B como entrada set_tris_d(0x00); //Puerto D como salida enable_interrupts(GLOBAL); //Se habilita la interrupción global enable_interrupts(INT_EXT); //Se habilita la interrupción externa ext_int_edge( H_TO_L ); //habilito el flanco interrupción de H a l puerto_D =0xFF; //inicializo puerto D //write_eeprom(0,0xFF);Resetear EEPROM while(true) { //Resto del programa } }

Page 56: 11 Ejercicios Micros en C V2

56

12. Control de varias interrupciones Para controlar varias interrupciones simultáneamente se va a utilizar la interrupción sobre la

cuenta del registro TMR0 y la interrupción al cambio de nivel de las líneas RB4 a RB7.

La aplicación establece la ejecución de las tres siguientes tareas a una velocidad tal que se

asemeja con una ejecución paralela:

• El LED D1 es intermitente con un tiempo de 1 seg.

• El LED D2 se prende por tres ciclos del LED D1 cuando se presiona una tecla cualquiera de

las líneas RB4 a RB7.

• El LED D3 es intermitente a una frecuencia correspondiente a la frecuencias de oscilación

del cristal dividido para 4 y una relación 1:256, el tiempo de prendido y apagado se establece

con la cuenta de TMR0 hasta producirse overflow.

El circuito a ser realizado se presenta a continuación:

El código de este ejemplo en Assembler se encuentra en el capítulo de “Manejo de

Interrupciones”, DBLINT.ASM, y en lenguaje C se muestra a continuación en DblInt.c:

/****************************************************************************** * * * DESCRIPCIÓN: Control de varias interrupciones * * DblInt.c * * * ******************************************************************************/ #include <16f877a.h> #use delay(clock=4000000) #fuses XT,NOWDT,NOPUT,NOPROTECT,NOLVP

Page 57: 11 Ejercicios Micros en C V2

57

#use fixed_io(b_outputs= PIN_B1, PIN_B2, PIN_B3) #define LED1 1 #define LED2 2 #define LED3 3 #byte PORTB = 0x06 int nTick; #int_RTCC void RTCC_isr() //realiza cambio de estado de D3 en cada overflow del TMR0 { if (bit_test(PORTB, LED3)) output_low(PIN_B3); else output_high(PIN_B3); } #int_RB //pone en 1 el led D2 al cambiar de estado RB4-RB7 void RB_isr() { output_high(PIN_B2); nTick = 6; } void main() { set_tris_b(0xF0); setup_counters (RTCC_INTERNAL, RTCC_DIV_256); enable_interrupts(INT_RTCC); enable_interrupts(INT_RB); enable_interrupts(GLOBAL); while(TRUE) { delay_ms(1000); //Cambio de estado del led D1 if (bit_test(PORTB, LED1)) output_low(PIN_B1); else output_high(PIN_B1); if (bit_test(PORTB, LED2)) { nTick--; if ( nTick == 0) output_low(PIN_B2); } } }

Análisis del código DBLINT.C

El control de interrupción verifica el evento generado por una interrupción, por lo cual, no es

necesario realizar una verificación para determinar qué evento fue generado, por lo que se

ejecuta la función correspondiente. Es nuestro caso sobre flujo del Timer0 y cambio de estado de

las patitas RB3, … RB0, respectivamente:

#int_RTCC RTCC_isr() #int_RB RB_isr()

Page 58: 11 Ejercicios Micros en C V2

58

El circuito en Proteus se presenta a continuación:

RA0/AN02

RA1/AN13

RA2/AN2/VREF-4

RA4/T0CKI6

RA5/AN4/SS7

RE0/AN5/RD8

RE1/AN6/WR9

RE2/AN7/CS10

OSC1/CLKIN13

OSC2/CLKOUT14

RC1/T1OSI/CCP216

RC2/CCP117

RC3/SCK/SCL18

RD0/PSP019

RD1/PSP120

RB7/PGD40

RB6/PGC39

RB538

RB437

RB3/PGM36

RB235

RB134

RB0/INT33

RD7/PSP730

RD6/PSP629

RD5/PSP528

RD4/PSP427

RD3/PSP322

RD2/PSP221

RC7/RX/DT26

RC6/TX/CK25

RC5/SDO24

RC4/SDI/SDA23

RA3/AN3/VREF+5

RC0/T1OSO/T1CKI15

MCLR/Vpp/THV1

U1

PIC16F877

X1CRYSTAL

R1

330

C1

15p

C2

15p

A

B

C

D

R6300

R7300

R8300

D1

LED-RED

R2

300

D2

LED-RED

R3

300

D3

LED-RED

R4

300

R9300

Page 59: 11 Ejercicios Micros en C V2

59

13. Comunicación serie entre dos PICs con la USART

En este ejemplo vamos a ver como comunicar dos PIC’s entre sí vía serie RS-232 haciendo uso

del modulo USART (Universal Synchronous/Asynchronous Receiver Transmiter.), que

incorporan la mayoría de los PIC’s de la gama media/alta. Con este modulo hardware se puede

implementar una comunicación serie del tipo síncrona ó asíncrona.

• Síncrona: necesita una conexión adicional para la señal de reloj. Una Usart hace de

Master y la otra de esclava. La comunicación es del tipo halfduplex (bidireccional por

turnos). Se emplea cuando se quiere comunicar un PIC con otro dispositivo electrónico,

como una memoria EEPROM externa.

• Asíncrona: no se necesita una conexión para la señal de reloj, los relojes del transmisor y

del receptor son independientes, aunque deben de tener la misma frecuencia, la

sincronización entre ambos se hace añadiendo unos bits adicionales (bit de inicio y bit de

parada) al byte de datos, que puede estar formado por 8 ó 9 bits. La comunicación puede

llegar a ser hasta dúplex completo (bidireccional simultanea). Este tipo de conexión es la

que se utiliza normalmente para comunicar un PIC con un Ordenador ó para comunicar

dos PIC’S entre sí, es la más usada y es la que vamos a ver a continuación como ejemplo.

CCS gestiona una comunicación serie a través de la directiva

#users232 (lista de parámetros)

Esta directiva se puede poner en cualquier parte del programa pero siempre después de:

#usedelay(Frecuencia)

Si nuestro PIC no tiene USART como es el caso del PIC 16f84A, CCS nos implementa la

comunicación serie por software y de forma totalmente transparente para el programador. Sin

embargo el utilizar un PIC con USART nos permite disponer de una serie de funcionalidades

adicionales, como el empleo de interrupciones para controlar el envío y recepción de datos serie.

Si queremos realizar una comunicación serie utilizando la USART del PIC, las conexiones

tendrán que ser a la fuerza las siguientes: RC6/TX/CK para la transmisión de datos y

RC7/RX/DT para la recepción de datos, según se muestra en la figura de abajo.

Page 60: 11 Ejercicios Micros en C V2

60

Los registros del PIC implicados en este recurso son los siguientes:

• PIR1–> Registro para señalizaciones ó banderas.

• RCSTA–> Registro para el control de la parte receptora de la USART

• TXREG–> Registro de datos de transmisión

• SPBRG–> Registro para el control del generador de frecuencia, encargado de generar la

frecuencia en baudios para realizar la transmisión.

• TXSTA–> Registro para el control de la parte de transmisión de la USART

• PIE1–> Habilitación de interrupciones

• RCREG--> Registro de datos de recepción

El circuito del ejemplo será es el siguiente:

Page 61: 11 Ejercicios Micros en C V2

61

El PIC U1 hará de PIC transmisor de datos, se encargará de comprobar si se pulsa alguna tecla

en el teclado y en el caso de que así sea, mandará el valor del carácter (1 byte) de la tecla

pulsada, correspondiente al código del teclado. El segundo PIC implementará en su código una

interrupción por recepción de datos serie en la USART, recibirá el carácter procedente de U1 y

visualizará su valor en un display de siete segmentos de cátodo común.

Código del programa

Al haber dos PIC’s tendremos que hacer dos programas independientes, uno para el transmisor y

otro para el receptor.

Transmisor: 01. 02./*--------------------------------------------------------*\ 03.| Comunicación entre dos PIC's por USART. Transmisor | 04.| | 05.| | 06.\*--------------------------------------------------------*/ 07. 08.#include <16F877.h> 09.#FUSES NOWDT, XT, NOPUT, NOPROTECT, NOBROWNOUT, NOLVP, NOCPD, NOWRT, NODEBUG 10.#use delay (clock = 4000000) 11.#include "kbd_2.c" 12.#use rs232(uart1, baud=9600)//usart1 -->ajuste de XMIT y RCV para la USART 1 13.void main() { 14. char c; 15. kbd_init(); //inizializo drivers teclado 16. while(true) 17. { 18. c = kbd_getc(); 19. if(c != 0 )//si se ha pulsado una tecla 20. { 21. putc(c);//Envía el caracter vía serie por la USART del PIC 22.

Page 62: 11 Ejercicios Micros en C V2

62

23. } 24. 25. } 26. 27.}

Comentario

En la parte del código del transmisor incluimos una librería nueva:

#include "kbd_2.c"

Esta librería es una modificación de KBDD.c que incluye CCS en la carpeta Drivers, creada en

la instalación del programa. La que viene por defecto controla un teclado de 4 (filas) X 3

(Columnas). La modificación que he hecho permite controlar un teclado de 4 X 4.

Viendo el código original es fácil hacer las modificaciones pertinentes para poder controlar un

teclado personalizado.

Solo son necesarias dos funciones para utilizar esta librería que son:

kbd_init(); //inicializa el drivers del teclado. kbd_getc();//devuelve el código ASCII de la tecla pulsada, si no se pulsa ninguna devuelve 0.

El uso de estas librerías tiene un problema y es que tienen copyright, si vemos el comentario en

el encabezado, nos encontramos con esto:

01./////////////////////////////////////////////////////////////////////////// 02.//// KBDD.C //// 03.//// Generic keypad scan driver //// 04.//// //// 05.//// kbd_init() Must be called before any other function. //// 06.//// //// 07.//// c = kbd_getc(c) Will return a key value if pressed or /0 if not //// 08.//// This function should be called frequently so as //// 09.//// not to miss a key press. //// 10.//// //// 11./////////////////////////////////////////////////////////////////////////// 12.//// (C) Copyright 1996,2003 Custom Computer Services //// 13.//// This source code may only be used by licensed users of the CCS C //// 14.//// compiler. This source code may only be distributed to other //// 15.//// licensed users of the CCS C compiler. No other use, reproduction //// 16.//// or distribution is permitted without written permission. //// 17.//// Derivative programs created using this software in object code //// 18.//// form are not restricted in any way. //// 19.///////////////////////////////////////////////////////////////////////////

Que nos viene a decir, entre otras cosas, que podemos crear programas derivados de este

software y distribuir el código objeto generado al compilar pero no se puede distribuir el código

fuente. Por lo que este es el motivo de no mostrar la modificación hecha sobre esta librería.

Para que os funcione el ejemplo al completo tendréis que modificar la librería para que sea capaz

de controlar el teclado 4X4 ó modificar el circuito para tener un teclado de 4X3.

Otra novedad de este ejemplo son los parámetros de configuración de la directiva:

Page 63: 11 Ejercicios Micros en C V2

63

#use rs232(uart1, baud=9600)//usart1 -->ajuste de XMIT y RCV para la USART 1

En el caso de utilizar la USART del pic, CCS nos permite configurar fácilmente el bit de

transmisión (XMIT) y el bit de recepción (RCV) dentro de la directiva #use rs232, el “1” es

porque hay PIC,s que tienen más de una USART.

Luego el programa entra en un bucle infinito para ir chequeando en cada momento si se ha

pulsado alguna tecla. Si detecta que se ha pulsado alguna tecla, envía el valor de la tecla pulsada

por el canal serie por medio de la finción putc().

Receptor: 01./*--------------------------------------------------------*\ 02.| Comunicación entre dos PIC's por USART. Receptor | 03.| | 04.| | 05.\*--------------------------------------------------------*/ 06. 07.#include <16F877.h> 08.#FUSES NOWDT, XT, NOPUT, NOPROTECT, NOBROWNOUT, NOLVP, NOCPD, NOWRT, NODEBUG 09.#use delay (clock = 4000000) 10.#byte puerto_D = 0x08 11.#use rs232(uart1, baud=9600) 12.char dato; 13. 14.#int_rda 15. void rd_isr(void)//función de interrupción por recepción de datos USART 16. { 17. dato= getc(); 18. 19. switch(dato)//visualizo el caracter recibido en el display 20. { 21. case '0': 22. puerto_D = 0x3F; 23. break; 24. case '1': 25. puerto_D = 0x06; 26. break; 27. case '2': 28. puerto_D = 0x5B; 29. break; 30. case '3': 31. puerto_D = 0x4F; 32. break; 33. case '4': 34. puerto_D = 0x66; 35. break; 36. case '5': 37. puerto_D = 0x6D; 38. break; 39. case '6': 40. puerto_D = 0x7D; 41. break; 42. case '7': 43. puerto_D = 0x07; 44. break; 45. case '8': 46. puerto_D = 0x7F; 47. break; 48. case '9': 49. puerto_D = 0x6F; 50. break; 51. case 'A': 52. puerto_D = 0xF7;

Page 64: 11 Ejercicios Micros en C V2

64

53. break; 54. case 'B': 55. puerto_D = 0x7C; 56. break; 57. case 'C': 58. puerto_D = 0x58; 59. break; 60. case 'D': 61. puerto_D = 0x5E; 62. break; 63. case 'E': 64. puerto_D = 0x79; 65. break; 66. case 'F': 67. puerto_D = 0x71; 68. break; 69. default: 70. 71. break; 72. } 73. } 74.void main() 75. { 76. 77. enable_interrupts(global);//Habilito interrupción USART 78. enable_interrupts(int_rda); 79. 80. set_tris_d( 0x00 ); // Puerto D como salida. 81. puerto_D =0x00; //inicializo puerto D 82. 83. while(true){ 84. 85. //Código principal 86. } 87. 88. }

Comentario

En la parte del código del receptor se utiliza el recurso de interrupción por recepción de datos de

la USART. Si utilizamos el asistente para crear el proyecto del receptor, tendremos que marcar la

opción que se muestra en la figura de abajo.

Page 65: 11 Ejercicios Micros en C V2

65

Cuando se produce la interrupción se obtiene el carácter recibido por medio de la función getc(),

después por medio de la sentencia switch se obtiene la salida para el display en el puerto D en

función del valor recibido.

Como siempre podemos ejecutar el programa paso a paso con Proteus y ver como CCS configura

los diferentes registros del PIC implicados en la comunicación serie.

En la figura de arriba se muestra por medio de la ventana Watch Windows como se pone a 1 el

bit RCIF (flag de interrupción por recepción de datos en la USART) del registro PIR1 en el

momento que se entra en la función de interrupción.

Nota: Realmente en este ejemplo la comunicación es unidireccional (simplex), ya que el PIC U2

no transmite datos solo recibe los datos procedente de U1, por lo que la conexión que une la

patilla de transmisión de U2 con la de recepción de U1 se podría suprimir en el circuito.

Aquí tienes un pequeño video del programa funcionando:

El código del circuito lo tienes aquí

/****************************************************************************** * * * * * DESCRIPCIÓN: Librería para el manejo de un teclado matricial 4x4. * * * ******************************************************************************/ #byte port_B = 6 #bit ROW_3 = 0x06.7 // Identificadores de bits #bit ROW_2 = 0x06.6 #bit ROW_1 = 0x06.5 #bit ROW_0 = 0x06.4 /* DEFINICIÓN DE CONSTANTES SIMBÓLICAS ****************************************/ #define COLUMN_0 1 #define COLUMN_1 2 #define COLUMN_2 4

Page 66: 11 Ejercicios Micros en C V2

66

#define COLUMN_3 8 #define NUMBER_COLUMNS 4 #define NUMBER_ROWS 4 #define DEBOUNCER_DELAY 30 /* DEFINICIÓN DE CONSTANTES ***************************************************/ char const matrixKeyBoard[ NUMBER_ROWS ][ NUMBER_COLUMNS ] = { /* Disposición del teclado */ '1', '2', '3', 'F', '4', '5', '6', 'E', '7', '8', '9', 'D', 'A', '0', 'B', 'C', }; /* DEFINICIÓN DE VARIABLES LOCALES ********************************************/ static unsigned column; /* FUNCIONES PÚBLICAS *********************************************************/ void mkd_int( void ) { /* Inicialización general */ set_tris_b( 0xF0 ); /* <RB7:RB4> como entrada, <RB3:RB0> como salida */ port_B = 0; column = 0; } char mkd_keyPress( void ) { static char keyPress = '\0'; /* Se ha de esperar aprox. 30 ms para evitar el rebote del teclado */ delay_ms( DEBOUNCER_DELAY ); /* Se comienza activando una columna */ switch( column ) { case 0: /* Se pone a 1 la columna 0 */ port_B = COLUMN_0; break; case 1: /* Se pone a 1 la columna 1 */ port_B = COLUMN_1; break; case 2: /* Se pone a 1 la columna 2 */ port_B = COLUMN_2; break; case 3: /* Se pone a 1 la columna 3 */ port_B = COLUMN_3; break; } /* Se rastrea si hay pulsación en la columna activa */ if( ROW_0 == 1 ) { keyPress = matrixKeyBoard[ 0 ][ column ]; } else if( ROW_1 == 1 ) { keyPress = matrixKeyBoard[ 1 ][ column ]; } else if( ROW_2 == 1 ) { keyPress = matrixKeyBoard[ 2 ][ column ]; }

Page 67: 11 Ejercicios Micros en C V2

67

else if( ROW_3 == 1 ) { keyPress = matrixKeyBoard[ 3 ][ column ]; } if( 3 == column ) { /* Se completo un ciclo, reiniciamos nuevamente */ column = 0; } Else { column++; } return( keyPress ); }

Las características de esta primera versión son:

• Sólo es válido para el puerto B, por lo tanto hay que tener en cuenta una posible inclusión

de resistencias PULLUP.

• Se mete un retardo de 30 ms para evitar el problema del rebote en las pulsaciones del

teclado. Este parámetro podríamos configurarlo en la llamada a la función de inicio.

Esta librería funciona perfectamente en Proteus (aunque habría que probarla en un circuito real),

además está perfectamente explicada. A continuación se presenta una pequeña modificación en

el código para que se pueda utilizar indistintamente en el Puerto B ó D: /******************************************************************************* * Modificación Nº1 de la Librería_Teclado 4X4 * * * * DESCRIPCIÓN: Esta modificación permite conectar el tecaldo al puerto B ó al D* * * *******************************************************************************/ //Para usar el puerto B descomenta la siguiente línea //#define uso_puertoB_teclado4x4 TRUE #if defined uso_puertoB_teclado4x4 #byte puerto = 6 #bit ROW_3 = 0x06.7 // Identificadores de bits puerto B #bit ROW_2 = 0x06.6 #bit ROW_1 = 0x06.5 #bit ROW_0 = 0x06.4 #else #byte puerto = 8 #bit ROW_3 = 0x08.7 // Identificadores de bits puerto D #bit ROW_2 = 0x08.6 #bit ROW_1 = 0x08.5 #bit ROW_0 = 0x08.4 #endif /* DEFINICIÓN DE CONSTANTES SIMBÓLICAS ****************************************/ #define COLUMN_0 1 #define COLUMN_1 2 #define COLUMN_2 4 #define COLUMN_3 8 #define NUMBER_COLUMNS 4 #define NUMBER_ROWS 4 #define DEBOUNCER_DELAY 30

Page 68: 11 Ejercicios Micros en C V2

68

/* DEFINICIÓN DE CONSTANTES ************************************/ char const matrixKeyBoard[ NUMBER_ROWS ][ NUMBER_COLUMNS ] = { /* Disposición del teclado */ '1', '2', '3', 'F', '4', '5', '6', 'E', '7', '8', '9', 'D', 'A', '0', 'B', 'C', }; /* DEFINICIÓN DE VARIABLES LOCALES *******************************/ static unsigned column; /* FUNCIONES PÚBLICAS *******************************************/ void mkd_int( char x ) {

switch( x ) {

case 'B': set_tris_b( 0xF0 ); /* <RB7:RB4> como entrada, <RB3:RB0> como salida */ break;

case 'D': set_tris_d( 0xF0);/* <RD7:RD4> como entrada, <RD3:RD0> como salida */ break;

} puerto = 0; column = 0;

} char mkd_keyPress( void ) {

static char keyPress = '\0'; /* Se ha de esperar aprox. 30 ms para evitar el rebote del teclado */ delay_ms( DEBOUNCER_DELAY ); /* Se comienza activando una columna */ switch( column ) {

case 0: /* Se pone a 1 la columna 0 */ puerto = COLUMN_0; break;

case 1: /* Se pone a 1 la columna 1 */ puerto = COLUMN_1; break;

case 2: /* Se pone a 1 la columna 2 */ puerto = COLUMN_2; break;

case 3: /* Se pone a 1 la columna 3 */ puerto = COLUMN_3; break;

}

/* Se rastrea si hay pulsación en la columna activa */ if( ROW_0 == 1 ) {

keyPress = matrixKeyBoard[ 0 ][ column ]; } else if( ROW_1 == 1 ) {

keyPress = matrixKeyBoard[ 1 ][ column ]; } else if( ROW_2 == 1 )

Page 69: 11 Ejercicios Micros en C V2

69

{ keyPress = matrixKeyBoard[ 2 ][ column ]; }

else if( ROW_3 == 1 ) {

keyPress = matrixKeyBoard[ 3 ][ column ]; } if( 3 == column ) {

/* Se completo un ciclo, reiniciamos nuevamente */ column = 0;

} else {

column++; } eturn( keyPress );

}

Programa principal:

*--------------------------------------------------------*\ | Comunicación entre dos PIC's por USART. Transmisor | | | | | \*--------------------------------------------------------*/ #include <16F877.h> #use delay (clock = 4000000) #FUSES NOWDT, XT, NOPUT, NOPROTECT, NOBROWNOUT, NOLVP, NOCPD, NOWRT, NODEBUG #use rs232(uart1, baud=9600)//usart1 -->ajuste de XMIT y RCV para la USART 1 #include "Libreria_Teclado4X4.c" void main() {

char c; //mkd_int('B'); //inizializo drivers teclado para puerto B mkd_int ('D'); //inicializo drivers teclado para puerto D while(true) {

c = mkd_keyPress(); if(c != 0 )//si se ha pulsado una tecla {

putc(c);//Envía el caracter vía serie por la USART del PIC }

} }

Ejemplo El siguiente ejemplo, almacena dos caracteres en la EEPROM del receptor:

TRANSMISOR

#include <16F628A.h> #FUSES NOWDT, XT, NOPUT, NOPROTECT, NOBROWNOUT, NOMCLR, NOLVP, NOCPD #use delay(clock=4000000) #use rs232(baud=9600,parity=N,xmit=PIN_B2,rcv=PIN_B1,bits=8)

Page 70: 11 Ejercicios Micros en C V2

70

void main() { char dato1='1'; char dato2='2'; putc(dato1); putc(dato2); }

RECEPTOR

#include <16F628A.h> #FUSES NOWDT, XT, NOPUT, NOPROTECT, NOBROWNOUT, NOMCLR, NOLVP, NOCPD #use delay(clock=4000000) #use rs232(baud=9600,parity=N,xmit=PIN_B2,rcv=PIN_B1,bits=8) //declaracion de variables globales int contador=0; #int_RDA void RDA_isr(void) { char cdecenas,cunidades; if (contador ==0) { cdecenas=getch(); write_eeprom(0,cdecenas); contador++; } else { cunidades=getch(); write_eeprom(1,cunidades); } } void main() { enable_interrupts(INT_RDA); enable_interrupts(GLOBAL); }

Y aquí tienes una imagen del circuito simulado:

Page 71: 11 Ejercicios Micros en C V2

71