Está en la página 1de 76

EJERCICIOS DE

MICROCONTROLADORES EN C

1
Contenido

1. Construcción de un simple secuenciador a LED ...................................................................................... 4

2. Encender un led más un pulsador ............................................................................................................. 7

3. Dos secuencias de luces más dos pulsadores ........................................................................................... 9

4. Contador Binario de 0-255 mostrado por leds ....................................................................................... 11

5. Contador de 0-9 con 7 segmentos .......................................................................................................... 13

6. Secuencia de luces utilizando funciones ................................................................................................ 15

7. Secuencia de luces utilizando operadores aritméticos ............................................................................ 17

8. Decodificador de Binario a 7 Segmentos ............................................................................................... 20

9. Multiplexación de Displays 7 Segmentos .............................................................................................. 23

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

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

12. Uso del TMR0 como contador utilizando interrupción ........................................................................ 33

13. Uso del TMR0 como temporizador utilizando interrupción ................................................................ 35

14. Uso del TMR0 como contador o temporizador utilizando interrupción .............................................. 37

15. Uso del TMR1 como Temporizador utilizando interrupción ............................................................... 40

16. Uso de la memoria EEPRO interna ...................................................................................................... 43

17. Control de varia interrupciones ............................................................................................................ 48

18. Mostrar un mensaje por un LCD conectado al puerto B ...................................................................... 51

19. Contador con LCD 2x16 conectada por puerto B ................................................................................ 53

20. Teclado 4x3 LCD ................................................................................................................................. 55

21. Teclado 4x4 y enviar el valor a una PC vía Rs232 (serial) y a un display 7 segmentos ...................... 58

22. Comunicación serie entre dos PICs con la USART ............................................................................. 62

23. Comunicación serie entre dos PICs con la USART y Teclado ............................................................ 65

24. Medir temperatura con LM35 y mostrarla por LCD 2x16 ................................................................... 70

25. Mandar un mensaje por Rs232 y mostrarlo por un LCD 2x16 ............................................................ 72

2
26. Medir temperatura con LM35 y mandar el valor vía RS232 (serial) ................................................... 74

BIBLIOGRAFÍA ............................................................................................................................................. 76

3
1. Construcción de un simple secuenciador a LED

Ejemplo:

Realizar un circuito para encender en forma secuencial un diodo LED en el pin RB0, cuya
frecuencia de intermitencia es de 1 seg. El PIC a utilizar es el PIC16F877 a una frecuencia de
oscilación de 4Mhz.

Análisis del problema:

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

El código de este ejemplo en lenguaje Assembly está disponible en el capítulo "Los Puertos
Paralelos de Entrada/Salida".

El código en lenguaje C es el siguiente:

/******************************************************************************
* *
* DESCRIPCIÓN: Frecuencia de intermitencia de 1 seg. *
* Led_On-Off.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:

4
Se va analizar línea por línea el contenido del código Led_On-Off.c, desde de la primera línea de
código.

 #include esta directiva del compilador del lenguaje C, indica al compilador que incluya en el
archivo fuente el texto que contiene el archivo especificado, en este caso la directiva que
permite definir el PIC a utilizar.
#include <16F877.h>

 #fuses es una directiva del compilador del lenguaje C que permite definir la palabra de
configuración del PIC, esta directiva define qué fusibles deben activarse o desactivarse en el
dispositivo cuando se programe. En este caso se informa al compilador que se usa un reloj en
modo XT, no usa Watch Dog y el código no es protegido:

#fuses XT,NOWDT,NOPROTECT

 #use delay es una directiva que sirve para establecer el reloj con el cual va a trabajar el PIC.
En este caso trabajará el PIC con un reloj de 4 MHz:

#use delay(clock=4000000)

 #use fixed es una directiva que indica al compilador C que genere código para hacer que un
pin de I/O sea de entrada o salida cada vez que se utiliza. En este caso PIN_B0 se configura
como salida:

#use fixed_io(b_outputs = PIN_B0)

NOTA: Cuando se utiliza la opción PIC_Wizard del compilador de C en la creación del


proyecto, no son necesarias las directivas: #include, #fuses y #use delay; porque en el
archivo Led_On-Off.h se configuran automáticamente.

 void main() es la función principal del programa, que tiene un lazo infinito que pone en alto
la salida RB0, a continuación hay un delay de 1 seg, y realiza 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);
}

El circuito en Proteus es el siguiente:

5
C1

22pF
U1 D1
R1
X1 13
OSC1/CLKIN RB0/INT
33
CRYSTAL 14 34 100
OSC2/CLKOUT RB1
35 LED-RED
C2 RB2
2 36
RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5
22pF 5 39
RA3/AN3/VREF+ RB6/PGC
6 40
RA4/T0CKI/C1OUT RB7/PGD
7
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
R2 8
RE0/AN5/RD RC1/T1OSI/CCP2
16
4k7 9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877

6
2. Encender un led más un pulsador

Ejemplo:
En este programa al presionar el pulsador conectado en el pin RA0 se encenderá un led conectado en el
pin RB0 por 0.5 seg, que estará dentro de un bucle infinito para que el programa se ejecute infinitamente.
El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de 4Mhz.

El código en lenguaje C es el siguiente:

#include "16f877.h" // PIC a utilizar

#use delay (clock=4000000) // Fosc=4Mhz

#fuses xt,nowdt,noprotect // Fusibles

#byte port_A=5 // Declara el puerto A en su direccion


#byte port_B=6 // Declara el puerto B en su direccion

void main(void)
{
set_tris_A(0b00000001); // Programa el puerto A para que RA0 sea entrada
set_tris_B(0b00000000); // Programa el puerto B para que sea salida
port_B=0x00; // En un inicio todo el puerto B estara apagado
while (true) // Bucle infinito para que el programa se ejecute infinitamente
{
if (input(pin_A0)==0) // Pregunta si RA0 es cero
{
port_B=0b00000001; // Cuando se cierra el pulsador ejecuta esta tarea
delay_ms(500); // la cual es encender un led conectado en RB0
port_b=0b00000000;
}
}
}

El circuito en Proteus es el siguiente:

7
5v

R1 U2 D1
13 33
R2
10k OSC1/CLKIN RB0/INT
14 34 220R
OSC2/CLKOUT RB1
35 LED-BLUE
RB2
2 36
RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5
5 39
RA3/AN3/VREF+ RB6/PGC
6 40
RA4/T0CKI/C1OUT RB7/PGD
7
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

8
3. Dos secuencias de luces más dos pulsadores

Ejemplo:
En este programa al presionar el pulsador conectado en el pin RA0 se hará una secuencia de luces por
el puerto B, y al presionar el pulsador conectado en el pin RA1 se hará otra secuencia de luces por el
puerto B. El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de 4Mhz.

El código en lenguaje C es el siguiente:

#include <16f877.h> // PIC a utilizar

#use delay(clock=4000000) // Cristal a utilizar

#fuses xt, nowdt, noprotect // Fusibles

#byte portA=5 // Declara el puerto A en su direccion


#byte portB=6 // Declara el puerto B en su direccion

int x=100; // A la variable x se adigna el valor de 100

void main()
{
set_tris_A(0b000011); // Programa para que RA0 y RA1 sean entradas
set_tris_B(0b00000000); // Programa para que todo el puerto B sea salida
portB=0b00000000; // En un inicio todo el puerto B estara apagado

for(;;) // Bucle infinito, similar al bucle while(true)


{
if(input(pin_A0)==0) // Pregunta si RA0 es cero
{
portB=0b00000001;
delay_ms(x); // La variable x tiene un valor de 100, el cual esta declarado
// al principio con la sentencia int x=100;
portB=0b00000010;
delay_ms(x);
portB=0b00000100;
delay_ms(x);
portB=0b00001000;
delay_ms(x);
portB=0b00010000;
delay_ms(x);
portB=0b00100000;
delay_ms(x);
portB=0b01000000;
delay_ms(x);
portB=0b10000000;
delay_ms(x);
portB=0b00000000;
}

if(input(pin_A1)==0) // Pregunta si RA1 es cero


{
portB=0b11111111;
delay_ms(x);
portB=0b00000000;
delay_ms(x);
portB=0b11111111;
9
delay_ms(x);
portB=0b00000000;
delay_ms(x);
portB=0b11111111;
delay_ms(x);
portB=0b00000000;
delay_ms(x);
}
}
}

El circuito en Proteus es el siguiente:


5v
D1
R1
U2 220R
R10 R9 13
OSC1/CLKIN RB0/INT
33 LED-GREEN
D2
14 34
10k 10k OSC2/CLKOUT RB1
35
R2
RB2
2 36 220R
RA0/AN0 RB3/PGM
3 37 LED-GREEN
4
RA1/AN1 RB4
38 D3
5
RA2/AN2/VREF-/CVREF RB5
39
R3
RA3/AN3/VREF+ RB6/PGC
6 40 220R
RA4/T0CKI/C1OUT RB7/PGD
7 LED-GREEN
RA5/AN4/SS/C2OUT
15 D4
8
RC0/T1OSO/T1CKI
16
R4
RE0/AN5/RD RC1/T1OSI/CCP2
9 17 220R
RE1/AN6/WR RC2/CCP1
10 18 LED-GREEN
RE2/AN7/CS RC3/SCK/SCL
23 D5
1
RC4/SDI/SDA
24
R5
MCLR/Vpp/THV RC5/SDO
25 220R
RC6/TX/CK
26 LED-GREEN
RC7/RX/DT D6
19
R6
RD0/PSP0
20 220R
RD1/PSP1
21 LED-GREEN
RD2/PSP2
22 D7
RD3/PSP3
27
R7
RD4/PSP4
28 220R
RD5/PSP5
29 LED-GREEN
RD6/PSP6
30 D8
RD7/PSP7 R8
PIC16F877A 220R
LED-GREEN

10
4. Contador Binario de 0-255 mostrado por leds

Ejemplo:
En este programa cada vez que se presiona el pulsador conectado en el pin RA0 se mostrará una cuenta
binaria de 0 a 255, a través de unos leds conectados en el puerto B. El programa debe tener un bucle
infinito para que se ejecute infinitamente. El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación
de 4Mhz.

El código en lenguaje C es el siguiente:

#include <16f877.h> // PIC a utilizar

#use delay(clock=4000000) // Cristal a utilizar

#fuses xt, nowdt, noprotect // Fusibles

#byte portB=6 // Declara el Puerto B en su direccion

int A=0; // Constante A con el valor inicial de cero

void main(void)
{
set_tris_b(0b00000000); // Programa todo el Puerto B como salida
portB=0b00000000; // En un inicio todo el Puerto B estara apagado

while(true) // Bucle Infinito


{
if(input(pin_A0)==0) // Pregunta si el pulsador conectado en RA0 es cero
{
A++; // Incrementa en 1 a la constante A
portB=A; // Muestra la variable A por el Puerto B
delay_ms(500); // Retardo de 500 mseg., para que la cuenta se visualice bien

if(A==256) // Pregunta si la variable A es 256, si es correcto A se asigna a cero


{ // y el programa vuelve a comenzar.
A=0;
}
}
}
}

El circuito en Proteus es el siguiente:

11
5v

D9
R10
R18 U2 220R
13 33 LED-GREEN
10k
14
OSC1/CLKIN RB0/INT
34 D10
OSC2/CLKOUT RB1
35
R11
RB2
2 36 220R
RA0/AN0 RB3/PGM
3 37 LED-GREEN
4
RA1/AN1 RB4
38 D11
5
RA2/AN2/VREF-/CVREF RB5
39
R12
RA3/AN3/VREF+ RB6/PGC
6 40 220R
RA4/T0CKI/C1OUT RB7/PGD
7 LED-GREEN
RA5/AN4/SS/C2OUT
15 D12
8
RC0/T1OSO/T1CKI
16
R13
RE0/AN5/RD RC1/T1OSI/CCP2
9 17 220R
RE1/AN6/WR RC2/CCP1
10 18 LED-GREEN
RE2/AN7/CS RC3/SCK/SCL
23 D13
1
RC4/SDI/SDA
24
R14
MCLR/Vpp/THV RC5/SDO
25 220R
RC6/TX/CK
26 LED-GREEN
RC7/RX/DT D14
19
R15
RD0/PSP0
20 220R
RD1/PSP1
21 LED-GREEN
RD2/PSP2
22 D15
RD3/PSP3
27
R16
RD4/PSP4
28 220R
RD5/PSP5
29 LED-GREEN
RD6/PSP6
30 D16
RD7/PSP7 R17
PIC16F877A 220R
LED-GREEN

12
5. Contador de 0-9 con 7 segmentos

Ejemplo:
En este programa cada vez que se presiona el pulsador conectado en el pin RA0 se irá incrementando
una cuenta de 0 a 9 y se visualizará por un display 7 segmentos. El programa debe tener un bucle infinito
para que se ejecute infinitamente. El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de
4Mhz.

El código en lenguaje C es el siguiente:

#include <16f877.h> // PIC a utilizar

#use delay(clock=4000000) // Cristal a utilizar

#fuses xt, nowdt, noprotect // Fusibles

#byte portB=6 // Declara el Puerto B en su direccion

void main(void)
{
int A=0; // Constante A con el valor inicial cero

set_tris_b(0b00000000); // Programa todo el Puerto B como salida


portB=0b00000000; // En un inicio todo el Puerto B estara apagado

while(true) // Bucle Infinito


{
if(input(pin_A0)==0) // Pregunta si el pulsador conectado en RA0 es cero
{
A++, // Incrementa en 1 a la constante A
portB=A; // Muestra la variable A por el Puerto B
delay_ms(500); // Retardo para que la cuenta se visualice bien

if(A==9) // Pregunta si la variable A es 9, si es correcto A se asigna a 255


{ // y el programa vuelve a comenzar.
A=255;
}
}
}
}

En la variable A se asigna el valor 255, porque si se asigna a 0, la cuenta empieza desde 1 hasta 9, y se
necesita que empiece desde 0 hasta 9.

El circuito en Proteus es el siguiente:

13
5v

R9 U2
10k 13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
35
RB2
2 36
RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5
5 39
RA3/AN3/VREF+ RB6/PGC
6 40
7
RA4/T0CKI/C1OUT RB7/PGD R1
RA5/AN4/SS/C2OUT
15
R2
8
RC0/T1OSO/T1CKI
16
R3
220R
9
RE0/AN5/RD RC1/T1OSI/CCP2
17
R4
220R
RE1/AN6/WR RC2/CCP1 220R
10 18 220R
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

14
6. Secuencia de luces utilizando funciones

Ejemplo:
En este proyecto al presionar el pulsador conectado en el pin RA0 se mostraáa una secuencia por el
puerto B, de igual manera al presionar el pulsador conectado en el pin RA1 se mostrará otra secuencia
por el puerto B. El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de 4Mhz.

La diferencia que tiene este proyecto con el PROYECTO 3 es que éste es realizado con FUNCIONES.

El código en lenguaje C es el siguiente:

#include <16f877.h> // PIC a utilizar

#use delay(clock=4000000) // Cristal a utilizar

#fuses xt, nowdt, noprotect // Fusibles

#byte portB=6 // Se declara el puerto B en su direccion

void funcion_1(void); // Prototipo de la funcion_1


void funcion_2(void); // Prototipo de la funcion_2

void main(void)
{
set_tris_B(0b00000000); // Se programa el puerto B como salida
portB=0b00000000; // En un inicio todo el puerto B estara apagado

while(true) // Bucle infinito


{
if(input(pin_A0)==0) // Pregunta si el pulsador en RA0 es cero
{
funcion_1();
}
if(input(pin_A1)==0) // Pregunta si el pulsador en RA1 es cero
{
funcion_2();
}
}
}

void funcion_1()
{
portB=0b00011000;
delay_ms(200);
portB=0b00100100;
delay_ms(200);
portB=0b01000010;
delay_ms(200);
portB=0b10000001;
delay_ms(200);
portB=0b00000000;
delay_ms(200);
}

void funcion_2()
{
portB=0b11111111;
15
delay_ms(200);
portB=0b01111110;
delay_ms(200);
portB=0b00111100;
delay_ms(200);
portB=0b00011000;
delay_ms(200);
portB=0b00000000;
delay_ms(200);
}

El circuito en Proteus es el siguiente:


5v

U2 R1
R12 R11 13
OSC1/CLKIN RB0/INT
33
R2
14 34
10k 10k OSC2/CLKOUT RB1
35
R3
220R
2
RB2
36
R4
220R
3
RA0/AN0 RB3/PGM
37
R6
220R
4
RA1/AN1 RB4
38
R7
220R
5
RA2/AN2/VREF-/CVREF RB5
39
R8
220R
6
RA3/AN3/VREF+ RB6/PGC
40
R9
220R
RA4/T0CKI/C1OUT RB7/PGD 220R
7 220R
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8
RE0/AN5/RD RC1/T1OSI/CCP2
16 D1 D2 D3 D4 D5 D6 D7 D8
9 17 LED-GREEN
LED-GREEN
LED-GREEN
LED-GREEN
LED-GREEN
LED-GREEN
LED-GREEN
LED-GREEN
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

16
7. Secuencia de luces utilizando operadores aritméticos

Los operadores aritméticos en los programas también se pueden utilizar para realizar
desplazamientos de bits a la izquierda y a la derecha, de la siguiente manera: una multiplicación
por 2 (en binario) 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.

Ejemplo:

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. El PIC a utilizar es el
PIC16F877 a una frecuencia de oscilación de 4Mhz.

El código en lenguaje C se muestra a continuación:

/******************************************************************************
* *
* Coche fantástico (utilizando operadores aritmeticos). *
* Desplaza el bit que está a uno hacia la izquierda y desplaza *
* el bit a uno hacia la derecha. *
* Secuencia_Luces_Op_Aritméticos.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)
{
puerto_b*=2;
delay_ms(1000);
}
while(puerto_b>=0b00000010)
{
puerto_b/=2;
delay_ms(1000);
}
}
}

Análisis del código:

Para poder explicar el código, se muestra a continuación con numeración en cada sentencia:

17
 En la línea 9: #byte puerto_b=6
Crea un identificador llamado puerto_b para referenciar el registro de propósito
específico (SFR) con dirección 0x06, que corresponde al Puerto B. Esta es una forma de
acceder a los puertos del PIC.

 En la línea 13: set_tris_b(0x00);


Configura el Puerto B del PIC como salida digital.

 En la línea 14: puerto_b=1;


Asigna el valor 1 (en binario 0b00000001) al Puerto B, o sea el pin RB0=1 y los demás
pines del Puerto B a cero.

El circuito en Proteus es el siguiente:

18
U1 RN1 U2
13 33 1 16 1 20
OSC1/CLKIN RB0/INT
14 34 2 15 2 19
OSC2/CLKOUT RB1
35 3 14 3 18
RB2
2 36 4 13 4 17
RA0/AN0 RB3/PGM
3 37 5 12 5 16
RA1/AN1 RB4
4 38 6 11 6 15
RA2/AN2/VREF-/CVREF RB5
5 39 7 10 7 14
RA3/AN3/VREF+ RB6/PGC
6 40 8 9 8 13
RA4/T0CKI/C1OUT RB7/PGD
7 9 12
RA5/AN4/SS/C2OUT
15 220 10 11
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17 LED-BARGRAPH-GRN
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

19
8. Decodificador de Binario a 7 Segmentos

Ejemplo:

En este ejemplo se realiza un decodificador de un número binario a decimal, mostrando los


resultados en un display de siete segmentos. El PIC a utilizar es el PIC16F877 a una frecuencia
de oscilación de 4Mhz.

Análisis del problema:

Circuito que hace las funciones de un decodificador de binario a decimal de un dígito decimal 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 activados en alto en su salida del Puerto B.

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

Luego se hace una tabla de verdad que muestre por cada combinación binaria en la entrada, a
qué pines del PIC se tienen que activar:

A2 A1 A0 Valor Salidas Activas Valor


Decimal 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 activa el conjunto de pines correspondientes
en el PIC que alimentarán los ánodos del display.

El código en lenguaje C se muestra a continuación:

/******************************************************************************

20
* *
* Decodificador de binario a decimal *
* Decodificador_Binario_7Segmentos.c *
* *
******************************************************************************/

#include <16F877.h>

#use delay( clock=4000000 )

#fuses XT, NOWDT

#byte port_A = 0x05 // Dirección del Puerto A.


#byte port_B = 0x06 // Dirección del Puerto B.

void main()
{
set_tris_b( 0x00 ); // Se configura Puerto B como salida.
set_tris_a( 0x3F ); // Se configura Puerto 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:
port_B = 0x7D;
break;
case 7:
port_B = 0x07;
break;
}
}
}

Análisis del código:

 Se incluye una librería nueva:

#include "kbd_lib.c"

Esta librería es una modificación de "kbd.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 se ha hecho permite controlar un teclado de 4 x 4. Al ver el

21
código original es fácil hacer las modificaciones pertinentes para poder controlar un teclado
personalizado.

NOTA: La configuración de las teclas se debe realizar de acuerdo al teclado a usar.

 Una forma más simple de seleccionar el valor decodificado para ser mostrado en el display
de 7 segmentos, que se utiliza en la sentencia switch(port_A){}, es asignando los valores
decodificados en un vector como se muestra a continuación:
int valor_B = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07};

Luego se envía al puerto así:

port_B = valor_B [port_A];

El circuito en Proteus es el siguiente:

U1 RN1
13 33 1 16
OSC1/CLKIN RB0/INT
14 34 2 15
OSC2/CLKOUT RB1
35 3 14
RB2
2 36 4 13
0 3
RA0/AN0
RA1/AN1
RB3/PGM
RB4
37 5 12
4 38 6 11
1 5
RA2/AN2/VREF-/CVREF
RA3/AN3/VREF+
RB5
RB6/PGC
39 7 10
6 40 8 9
1 7
RA4/T0CKI/C1OUT
RA5/AN4/SS/C2OUT
RB7/PGD
15 RX8
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

22
9. Multiplexación de Displays 7 Segmentos

Ejemplo:

Este programa cuenta de 0 al 99 que será mostrado en dos displays de 7 segmentos


multiplexados. El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de 4Mhz.

El código en lenguaje C se muestra a continuación:

/******************************************************************************
* *
* Multiplezar dos Displays. *
* Multiplexación_7Segmentos.c *
* *
******************************************************************************/

#include <16F877.h>

//Configuración de los fusibles.


#FUSES XT, NOWDT

#use delay(clock=4000000) //Frecuencia de reloj 4MHz

#byte puerto_B = 0x06 // Identificador para el puerto B.


#byte puerto_C = 0x07 // Identificador para el puerto C.

#define bit0_C puerto_C.0 // Definición del bit 0 del puerto C.


#define bit1_C puerto_C.1 // Definición del bit 1 del puerto C.

byte CONST DISPLAY[10]={ // Arreglo de 10 elementos con los valores de 0 10


0x3F,0x06,0x5B,0x4F,0x66, // para el decodificador de 7 segmentos
0x6D,0x7D,0x07,0x7F,0x67};

int cuenta; // Variable de la cuenta de 0 a 99


long retardo; // Variable para el retardo de más omenos 0.5 seg.

void Mostrar_Dato()
{
int uni,deci;

deci=cuenta/10;
uni=cuenta%10;

puerto_C=0b00000001;
puerto_B=(DISPLAY[uni]);
delay_ms(5);

puerto_C=0b00000010;
puerto_B=(DISPLAY[deci]);
delay_ms(5);
}

void Delay_1seg()
{
retardo=250;
while(retardo>0)
{
Mostrar_Dato();
retardo--;
}
}

void main()
23
{
port_b_pullups(true); // Activa los pull ups del puerto B

set_tris_b(0x00); //Puerto B como salida


set_tris_c(0x00); //Puerto C como salida

cuenta=0;
while(true)
{

if (cuenta<100)
{
Delay_1seg();
cuenta++;
}
else
cuenta=0;
}
}

El circuito en Proteus es el siguiente:

RN1
1 16
2 15
3 14
4 13
5 12
6 11
7 10
C3 8 9

RX8
22pF U1
13 33
OSC1/CLKIN RB0/INT
X2 14
OSC2/CLKOUT RB1
34
CRYSTAL 35
RB2
2 36
C4 RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5
5 39
RA3/AN3/VREF+ RB6/PGC
22pF 6 40
RA4/T0CKI/C1OUT RB7/PGD
7
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
RC4/SDI/SDA
23 R3 R4
1 24 1k 1k
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
RD1/PSP1
20
R1 Q1 Q2
21 BC548C BC548C
RD2/PSP2
22 220
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
R2
RD6/PSP6
30 220
RD7/PSP7
PIC16F877A

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

Una señal externa que se aplica al pin RB0/INT produce la interrupción externa en el PIC 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) o el de bajada (cuando pasa de 1 a 0).

Ejemplo:

En este ejemplo se usa la interrupción externa a través del pin RB0/INT, para implementar una
alarma sencilla, que funciona de la siguiente manera:

 Cuando se pulse el sensor de alarma RB0 en la transición de alto a bajo y no esté cerrado el
interruptor de desactivación de alarma RB1, se pondrá a uno el pin 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.

 Una vez activada la alarma, permanecerá activa hasta que no desactivarla por medio del
interruptor de desactivación de alarma (interruptor cerrado).

El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de 4Mhz.

El código en lenguaje C se muestra a continuación:

/*--------------------------------------------------------*\
| Ejemplo uso de interrupción externa por RB0 |
| |
| InterrupcionRB0.c |
\*--------------------------------------------------------*/

#include <16F877.h>

#FUSES NOWDT //No utiliza el timer Watch Dog


#FUSES XT //Oscilador XT <= 4mhz
#FUSES NOPROTECT //código 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 el Puerto B
RB7=0; //inicializa RB7
enable_interrupts(GLOBAL); //habilita interrupción global
enable_interrupts(INT_EXT); //habilita interrupción externa

25
ext_int_edge( H_TO_L );//habilita el flanco interrupción de H a L

while(true); //Bucle infinito hasta interrupción


}

Análisis del código:

 Para activar una carga con una potencia considerable, es necesario adaptar el circuito de
salida para controlar con los pocos miliamperios que da el PIC la carga que se desea. Hay
muchas formas de hacer esto, se puede utilizar un par de transistores en configuración
Darlington y atacar directamente el relé de potencia o no utilizar relés y controlar la carga
por medio de un Triac de potencia.

 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 se puede mapear un
bit en concreto de un registro. Aunque CCS incluye funciones para el manejo de bits, está
opción es muy cómoda de utilizar y hará que el código sea más portable para utilizar en otros
compiladores.

 En la función de interrupción se incluye el código que se quiere ejecutar, cuando se active la


interrupción externa por el pin RB0. En este 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 desactivarla por medio del interruptor. El bosquejo de la
función de interrupción externa por el pin RB0 es la siguiente:
#int_EXT
void EXT_isr(void)
{
// Codigo de la interrupcion
}

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


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

 Dentro del bloque principal de la función main(), se tiene 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 se


quiere que sea en el flanco descendente se hace por medio de la función:

ext_int_edge(flanco)

Por ejemplo:

ext_int_edge(L_TO_H); // Flanco de subida.


ext_int_edge(H_TO_L); // Flanco de bajada.

Si no se quiere utilizar las funciones de CCS. Se puede modificar directamente el bit


INTEDG. Por ejemplo:

#bit INTEDG=0x81.6

26
INTEDG=1; // Flanco ascendente
INTEDG=0; // Flanco descendente

Análisis de los bits de interrupción

Para ver como CCS gestiona está interrupción, se simula el circuito en Proteus cargando el
archivo .cof en el PIC, para que permita la simulación paso a paso. Se para la simulación en el
bucle while de la función principal obteniéndose lo que se muestra en la siguiente figura:

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.
 INTF=0 , Señalización de interrupción.

Si se pone un Breakpoint en el if que hay dentro de la rutina de interrupción y se pulsa 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 porque
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.

27
El bit de señalización INTF no fue usado en este ejemplo, pero en cualquier otro ejemplo se
puede leer su valor y utilizarlo para lo que se desea.

El circuito en Proteus es el siguiente:

R2
330R
C1 Sensor de Alarma
R3
1k
15p
U1
13 33 Activación/Desactivación de Alarma
OSC1/CLKIN RB0/INT
X1 14
OSC2/CLKOUT RB1
34
CRYSTAL 35
RB2
2 36
C2 RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5
5 39
RA3/AN3/VREF+ RB6/PGC
15p 6 40 12 V 12 V
RA4/T0CKI/C1OUT RB7/PGD
7
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8 16
9
RE0/AN5/RD RC1/T1OSI/CCP2
17 Micro-Relé
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1
MCLR/Vpp/THV RC5/SDO
24 RL1
RC6/TX/CK
25 D1 12V
26 R4 1N4007
RC7/RX/DT

R1 RD0/PSP0
19 330R
330 20
RD1/PSP1 R.Potencia
RD2/PSP2
21 Q1 Alarma
22 BC547
RD3/PSP3
27
RD4/PSP4
28 RL2 L1
RD5/PSP5 12V
29 12V
RD6/PSP6
RD7/PSP7
30 D2
1N4007
PIC16F877A

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

Para que se produzca la interrupción por cambio del estado lógico en alguna de las 4 líneas de
mayor peso del Puerto B (RB7-RB4) del PIC, dichas líneas tienen que estar previamente
configuradas como entradas. Este recurso hardware es muy utilizado para el control de teclados.

Ejemplo:

En este ejemplo 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. El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de
4Mhz.

Análisis del problema:

Los cuatro bits menos significativos del Puerto B en el circuito están fijados a uno mediante la
resistencia R0, por ejemplo si se pulsa el pulsador 1 se pone RB4=0 y el valor binario a la
entrada del Puerto B será:

RB7 RB6 RB5 RB4 RB3 RB2 RB1 RB0


1 1 1 0 1 1 1 1

Que en decimal tendrá un valor de 239, de la misma forma se determinan los valores para las
otras posibles entradas, teniendo en cuenta que solo un pulsador esté pulsado; el resto de valores
son: 223, 191 y 127.

En la siguiente tabla se muestran los valores a utilizar para configurar el display de siete
segmentos en cátodo común.

Valor
Display Dígito g f e d c b a
Hex.
0 011 1 1 1 1 3F
1 000 0 1 1 0 06
2 101 1 0 1 1 5B
3 100 1 1 1 1 4F
4 110 0 1 1 0 66
5 1 1 0 1 1 0 1 6D
6 1 1 1 1 1 0 1 7D
7 000 0 1 1 1 07
8 111 1 1 1 1 7F
9 110 1 1 1 1 6F

El código en lenguaje C se muestra a continuación:


29
/*--------------------------------------------------------*\
| Uso de la interrupción por cambio en los pines RB4-RB7 |
| del PIC. |
| Interrupción _RB4-RB7.c |
\*--------------------------------------------------------*/

#include <16F877.h>

//Configuración de los fusibles.


#FUSES XT, NOPROTECT, NOCPD

#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 interrupcion
{
switch(puerto_B)
{
case 239:
puerto_D = 0x06;
break;
case 223:
puerto_D = 0x5B;
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 interrupcion particular


enable_interrupts(GLOBAL); // Habilita interrupción global

puerto_D=0x00; // Inicializa puerto D

while(True)
{
//codigo principal
}
}

Análisis del Código:

 Dentro de la función principal se configura 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 se conecta el display de siete
segmentos (como es de cátodo común, sus entradas serán activas con nivel alto).

 Se habilita la interrupción global y particular, por medio de las siguientes instrucciones:


30
enable_interrupts(INT_RB);
enable_interrupts(GLOBAL);

 Se inicializa el puerto D con el valor 0.

puerto_D=0x00;

 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 se chequea el valor en decimal del puerto B por medio
de una sentencia de control switch y según sea la tecla pulsada muestra su valor en el
display.

Para ver como CCS gestiona está interrupción, se simula el circuito en Proteus cargando el
archivo .cof en el PIC, para que permita la simulación paso a paso, al igual que se hizo con el
ejemplo de la interrupción externa por RB0/INT. Para parar el programa dentro de la función
de interrupción se puede 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 los pines 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, da igual como
esté porque la global está deshabilitada.
 RBIF=1 , Señalización por cambio de estado de los pines 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.
 RBIF=0 , Señalización por cambio de estado de los pines 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:

31
"Es necesario una lectura (ó escritura) del Puerto B cuando se produzca esta interrupción, en este
ejemplo no hay problema porque nada más entrar en la función de interrupción se lee el estado
del puerto por medio de la sentencia switch (puerto_B). Pero en el ejemplo de la alarma que se
hizo por activación de la interrupción externa RB0/INT, cuando la alarma está desactivada y se
pulsa el sensor de alarma, la secuencia del programa entra en la función de interrupción pero no
hace nada dentro de ella, si se implementaría ese mismo circuito pero con cuatro sensores de
alarma y utilizando la interrupción por cambio de estado en RB4:RB7, si no se incluye una
lectura del puerto dentro de la función de interrupción, aunque no sirva para nada, el programa se
vuelve loco y no funciona bien este recurso del PIC".

El circuito en Proteus es el siguiente:

C1
R0 R1 R2 R3 R4
15p 330R 330R 330R 330R 330R
U1
13 33
OSC1/CLKIN RB0/INT
X1 14
OSC2/CLKOUT RB1
34
35
CRYSTAL
2
RB2
36
1
C2 RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
5
RA2/AN2/VREF-/CVREF RB5
39
2
RA3/AN3/VREF+ RB6/PGC
15p 6 40
RA4/T0CKI/C1OUT RB7/PGD
7
RA5/AN4/SS/C2OUT
15
3
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
10
RE1/AN6/WR RC2/CCP1
18
4
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT RN1
R5 RD0/PSP0
19 1 16
330 20 2 15
RD1/PSP1
21 3 14
RD2/PSP2
22 4 13
RD3/PSP3
27 5 12
RD4/PSP4
28 6 11
RD5/PSP5
29 7 10
RD6/PSP6
30 8 9
RD7/PSP7
PIC16F877A RX8

32
12. Uso del TMR0 como contador utilizando interrupción
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 del
pin RA4/TOCKI se le llama contador.

Ejemplo:

Utilizar el TIMER0 con una frecuencia de reloj externa al microcontrolador, la señal externa se
aplica al pin RA4/TOCKI del PIC, dicha señal se la utilizará para generar una interrupción a
través del TIMER0 cada segundo, en la función de interrupción se implementará el código
necesario para hacer parpadear un Led en el pin RB7 del PIC. El PIC a utilizar es el PIC16F877 a
una frecuencia de oscilación de 4Mhz.

La frecuencia de la señal de reloj externa que se utilizará será de 400 Hz y el TIMER0 se lo


configura para que empiece a contar en el flanco de subida de la señal de reloj.

Análisis de programa:

El cálculo del tiempo de interrupción (overflow) del TIMER0 de 1 segundo con una frecuencia
de reloj externa de 400 Hz (no está multiplicada por 4) y un Preescaler de 4 (se podía a ver
elegido otro cualquiera que estuviera en la tabla), se utiliza con la siguiente fórmula:

Entonces, para calcular el valor con el que se tiene que inicializar el TMR0 (N1) para que se
produzca su desbordamiento cada segundo, se despeja ValorTMR0 obteniéndose un valor de 156
que en hexadecimal es de 0x9C.

La función en C para configurar el TMR0 es:

setup_timer_0(RTCC_EXT_L_TO_H|RTCC_DIV_4);

Las palabras utilizadas en esta función para declarar los parámetros definen ya su significado.

El código en lenguaje C se muestra a continuación:

#include <16F877.h>
#fuses XT,NOWDT
#use delay(clock=4000000)

#bit RB7=0x6.7 //define RB7 como el pin 7 del Puerto B

#int_TIMER0 //la siguiente función tiene que ser la de interrupción del TMR0
void TIMER0_isr(void) //función interrupción TMR0
{
if (RB7==0)
{

33
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 y el resto como entrada


RB7=0; //inicializa 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);
}

El circuito en Proteus es el siguiente:

C1

15p U1
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
X1 1
MCLR/Vpp/THV RB2
35
CRYSTAL 36
RB3/PGM A
2 37
C2 RA0/AN0 RB4
3 38
RA1/AN1 RB5 B
4 39
5
RA2/AN2/VREF- RB6/PGC
40
R2
RA3/AN3/VREF+ RB7/PGD C
15p 6 300
RA4/T0CKI
7 15
RA5/AN4/SS RC0/T1OSO/T1CKI D
16
RC1/T1OSI/CCP2
8 17
RE0/AN5/RD RC2/CCP1
R3 9
RE1/AN6/WR RC3/SCK/SCL
18 D1
10k 10 23 LED-RED
RE2/AN7/CS RC4/SDI/SDA
24
RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
U1(RA4/T0CKI)
PIC16F877

34
13. Uso del TMR0 como temporizador utilizando interrupción

Ejemplo:

Este ejemplo usa el TIMER0 para generar una interrupción cada 32 mseg utilizando el reloj
interno del microcontrolador. La función de interrupción se utilizará para hacer parpadear un Led
en el pin RB7 del PIC. El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de 4Mhz..

Análisis del problema:

El cálculo del tiempo de interrupción (overflow) del TIMER0 con una frecuencia de reloj
interna, se utiliza con la siguiente fórmula:

En la fórmula anterior se tiene Interrupt_TMR0 = 32 mseg, que es el valor que se fija en el


ejemplo, también se tiene F.Oscilador = 4 MHz y quedan dos valores para tener resueltas todas
las variables de la ecuación: el ValorTMR0 que se puede sobrescribir su valor en cualquier
momento durante la ejecución del programa y el Preescaler de 256 (el valor puede ser el que se
quiera dar al Preescaler).

Entonces, para calcular el valor con el que se tiene que inicializar el TMR0 (N1) para que se
produzca su desbordamiento cada 32 mseg, se despeja ValorTMR0 obteniéndose un valor de 131
que en hexadecimal es 0x83.

El código en lenguaje C se muestra a continuación:

#include <16F877.h>

#use delay(clock=4000000)

#bit RB7=0x6.7 //define RB7 como el pin 7 del 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
}
}

35
void main()
{
set_tris_b(0b01111111); //configura RB7 como salida y el resto como entrada
RB7=0; //inicializa 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);
}

Análisis del programa:

El funcionamiento del programa es muy simple, después de inicializar el puerto B se inicializa 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 se obtuvo del cálculo anterior,
como está activada la interrupción por desbordamiento del contador este empieza a contar a
partir de 0x86 o 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, en cada caso se vuelve a
cargar el valor del TMR0 para que el contador vuelva a empezar a contar desde 131 y no de cero,
de esta manera la interrupción se producirá cada 32 mseg que es lo que se desea.

El circuito en Proteus es el siguiente:

C1

15p U1
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
X1 1
MCLR/Vpp/THV RB2
35
CRYSTAL 36
RB3/PGM A
2 37
C2 RA0/AN0 RB4
3 38
RA1/AN1 RB5 B
4 39
5
RA2/AN2/VREF- RB6/PGC
40
R2
RA3/AN3/VREF+ RB7/PGD C
15p 6 300
RA4/T0CKI
7 15
RA5/AN4/SS RC0/T1OSO/T1CKI D
16
RC1/T1OSI/CCP2
8 17
RE0/AN5/RD RC2/CCP1
9
RE1/AN6/WR RC3/SCK/SCL
18 D1
R1 10
RE2/AN7/CS RC4/SDI/SDA
23 LED-RED
10k 24
RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877

36
14. Uso del TMR0 como contador o temporizador utilizando
interrupción

Ejemplo:

En este ejemplo utiliza el TMR0 como contador y temporizador, seleccionado mediante un


switche en el pin RB6 del PIC. Generar una interrupción cada 32 mseg mediante el reloj interno
y generar una interrupción cada 1 seg con la frecuencia de reloj externa de 400 Hz que se aplica
el pin RA4/TOCKI del PIC; y el TIMER0 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 el pin RB7 del PIC. El PIC a utilizar es el PIC16F877 a
una frecuencia de oscilación de 4Mhz.

Análisis del problema:

1. Utilizar el TIMER 0 como temporizador. Utilizar el Timer0 para generar una interrupción
cada 32 mseg, mediante el reloj interno del microcontrolador. La función de interrupción se
utilizará para hacer parpadear un LED en el pin RB7 del PIC. La frecuencia de la señal de
reloj que se utiliza será de un cristal 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 mseg 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 mseg:

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

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

setup_timer_0(RTCC_INTERNAL|RTCC_DIV_256);

2. Utilizar el TIMER0 como contador. La frecuencia de reloj externa al microcontrolador, se


aplica el pin 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 el pin RB7 del PIC. La frecuencia de la señal de

37
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.

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 lenguaje 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 seg, esto es lo
que saldría en la fórmula.

El código en lenguaje C se muestra a continuación:

#include <16F877.h>
#use delay(clock=4000000)

#bit RB6=0x6.6 // Define RB6 como el pin 6 del puerto B


#bit RB7=0x6.7 // Define RB7 como el pin 7 del puerto B

#int_TIMER0
void TIMER0_isr(void)
{
if (RB6)
{
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;

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

El circuito en Proteus es el siguiente:

C1

15p U1 R3
13 33 300
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
X1 1
MCLR/Vpp/THV RB2
35
CRYSTAL 36
RB3/PGM
2 37
C2 RA0/AN0 RB4
3 38
RA1/AN1 RB5
4 39
5
RA2/AN2/VREF- RB6/PGC
40
R2
RA3/AN3/VREF+ RB7/PGD
15p 6 300
RA4/T0CKI
7 15
RA5/AN4/SS RC0/T1OSO/T1CKI
16
RC1/T1OSI/CCP2
8 17
RE0/AN5/RD RC2/CCP1
R4 9
RE1/AN6/WR RC3/SCK/SCL
18 D1
10k 10 23 LED-RED
RE2/AN7/CS RC4/SDI/SDA
24
RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0 A
20
RD1/PSP1
21
RD2/PSP2 B
22
RD3/PSP3
27
RD4/PSP4 C
28
RD5/PSP5
29
RD6/PSP6 D
30
RD7/PSP7
U1(RA0/AN0)
PIC16F877

39
15. Uso del TMR1 como Temporizador utilizando interrupción

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 los
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 los registros TMR1H y TMR1L se incrementan
desde 0000h a FFFFh, y una vez que llega a su máximo valor empieza otra vez desde 0
notificando de ello por medio de la bandera TMR1F.

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


programa entra en la función de tratamiento a la interrupción.

Algunas características de este Timer1 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 como temporizador, externo como contador), se lo configura 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 preescaler seleccionado.
 La fórmula para determinar los tiempos del Timer1 cuando es utilizado como temporizador
(reloj interno) es la siguiente:

 El paso del contador vendrá determinado por:

Paso_Contador= 4/Frecuencia_Oscilación x Preescaler

 El Timer1 se puede habilitar o deshabilitar por medio del bit TMR1ON del registro
T1CON.

Ejemplo:

En este ejemplo se utiliza 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. El PIC a utilizar es el PIC16F877 a
una frecuencia de oscilación de 4Mhz.

Análisis del problema:

40
La resolución es el tiempo que tarda el contador en incrementar su valor, es decir, el paso del
contador. Este valor depende del Preescaler seleccionado y de la frecuencia del reloj principal.
Por ejemplo, si se selecciona la última opción corresponde al preescaler de 8 y se selecciona una
frecuencia de 4MHz para el reloj principal, el paso del contador será igual a:

4/Frecuencia_Oscilación x Preescaler= 4/4MHz x 8= 8 useg.

El Overflow del Timer1 es el tiempo que tardará el contador en desbordarse. Suponiendo que se
carga el TMR1 con valor 0, que es como viene por defecto, al aplicar la fórmula se obtiene el
valor de 524 mseg (desbordamiento_Timer1= 4/4MHz x 8(65536-0)= 524 mseg).

En vez de 524 mseg, se quiere obtener 500 mseg, se sustituye ese valor en la fórmula y
despejando el valor a cargar en el TMR1, se tiene:

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

El código en lenguaje C se muestra a continuación:

#include <16F887.h>//Pic utilizado

//Palabra de configuración de los fusibles.


#FUSES XT, NOPROTECT
#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 y el resto como entrada
RB7=0;//Inicializa RB7
setup_timer_1(T1_INTERNAL|T1_DIV_BY_8);//Setup timer: Reloj interno, preescaler= 8
enable_interrupts(INT_TIMER1);//Habilita interrupción particular del TIMER1
enable_interrupts(GLOBAL);//Habilita interrupciones globales
set_timer1(0x0BDC);//Carga del TMR1
while(true);//Bucle infinito hasta la interrupción
}

Análisis del programa:

En el programa está explicado con los comentarios hechos en el código fuente. Falta 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:
41
o T1_DISABLED: deshabilita el Timer1, esto permite un ahorro de energía en el PIC si
no se utiliza este recurso.
o T1_INTERNAL: fuente de reloj principal. Modo temporizador.
o T1_EXTERNAL: fuente de reloj externa. El timer1 funcionará como contador y como
condición para empezar el contaje debe producirse en un flanco de bajada en la señal
de reloj, ver la siguiente figura:

Este pequeño detalle que parece poco importante, no lo es en el caso de que se quiera
contar de manera precisa los pulsos de entrada.

o T1_DIV_BY_1, T1_DIV_BY_2, T1_DIV_BY_4, T1_DIV_BY_8: constantes para


seleccionar el preescaler que se desea seleccionar.

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


muestra e4n los siguientes ejemplos:

setup_timer_1 ( T1_DISABLED );
setup_timer_1 ( T1_INTERNAL | T1_DIV_BY_4 );
setup_timer_1 ( T1_INTERNAL | T1_DIV_BY_8 );

El circuito en Proteus es el siguiente:

C1

15p U1
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
X1 1
MCLR/Vpp/THV RB2
35
A
CRYSTAL 36
RB3/PGM
2 37
C2 RA0/AN0 RB4 B
3 38
RA1/AN1 RB5
4 39
5
RA2/AN2/VREF- RB6/PGC
40
R2 C
RA3/AN3/VREF+ RB7/PGD
15p 6 300
RA4/T0CKI D
7 15
RA5/AN4/SS RC0/T1OSO/T1CKI
16
RC1/T1OSI/CCP2
8 17
RE0/AN5/RD RC2/CCP1
R1 9
RE1/AN6/WR RC3/SCK/SCL
18 D1
10k 10 23 LED-RED
RE2/AN7/CS RC4/SDI/SDA
24
RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877

42
16. Uso de la memoria EEPRO interna
La memoria EEPROM de datos no está mapeada en la zona de la memoria de datos de propósito
específico y general, donde se ubican los registros SFR y GPR.

CCS permite separar por completo del proceso de lectura y escritura de la memoria EEPROM, lo
único que se tiene que saber es las funciones que se deben aplicar y los parámetros y valores que
devuelven dichas funciones. 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 o 16 bits. Dependiendo del PIC a utilizar se dispone de más o 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 bits.

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 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 homóloga a la anterior pero que
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 aporta una serie de funciones para trabajar fácilmente con este tipo de
memorias, estas funciones son algunas de ellas, pero todavía hay más.

Algunas consideraciones a tener en cuenta sobre las memorias EEPROM:

 Son rápidas en el proceso de lectura, pero pueden tardar varios mseg en realizar un proceso
de escritura.
 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.

Ejemplo:

43
En este ejemplo se va a utilizar las funciones básicas para leer y escribir datos en la memoria
interna EEPROM del PIC: read_eeprom (address) y write_eeprom (address, value), para lo cual
se va a realizar un contador del tipo "su turno" que se encuentra 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
PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de 4Mhz.

Analizar el problema:

Cuando se quiere utilizar más de un display para minimizar el número de pines en el PIC, hay
varias formas de hacer su control, a continuación se muestran dos formas:

1. Una de ellas es utilizar un decodificador BCD a 7 segmentos como el 7447.

2. Otra forma es multiplexar las líneas de datos (la que se ha utilizado en este ejemplo), 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
ahorra los decodificadores, pero si se utiliza más de cuatro display, se nota un parpadeo
molesto.

El código en lenguaje C se muestra a continuación:

/******************************************************************************
* *
* DESCRIPCIÓN: Uso de la memoria EEPROM del PIC *
* Memoria_EEPROM.c *
* *
******************************************************************************/

#include <16F877.h>

// Configuracion de los fusibles.


#FUSES XT, NOWDT

#use delay(clock=4000000) // Frecuencia de reloj 4MHz

#byte puerto_B = 0x06 // Identificador para el puerto B.


#byte puerto_C = 0x07 // Identificador para el puerto C.
#byte puerto_D = 0x08 // Identificador para el puerto D.

#define bit0_D puerto_D.0 // Definición del bit 0 del puerto D.


#define bit1_D puerto_D.1 // Definición del bit 1 del puerto D.

byte CONST DISPLAY[10]={ // Arreglo de 10 elementos con los valores de


0x3F,0x06,0x5B,0x4F,0x66, // 0 a 10 para el decodificador de 7 segmentos
0x6D,0x7D,0x07,0x7F,0x67};

int cuenta; // Variable de la cuenta de 0 a 99


long retardo; // Variable para el retardo de 1 seg.

void Mostrar_Dato();
void Delay_1seg();

#int_EXT
void EXT_isr( void )
{
if ((read_eeprom(0)==0x99)||(read_eeprom(0)==0xFF))
{
write_eeprom(0,0);
cuenta=read_eeprom(0);

44
}
else
{
write_eeprom(0,(read_eeprom(0))+1);
cuenta=read_eeprom(0);
}
Delay_1seg();
}

void Mostrar_Dato()
{
int uni,deci;

deci=cuenta/10;
uni=cuenta%10;

puerto_D=0b00000001;
puerto_C=(DISPLAY[uni]);
delay_ms(5);

puerto_D=0b00000010;
puerto_C=(DISPLAY[deci]);
delay_ms(5);
}

void Delay_1seg()
{
retardo=250;
while(retardo>0)
{
Mostrar_Dato();
retardo--;
}
}

void main()
{
port_b_pullups(true); // Activa los pull ups del puerto B

set_tris_b(0xFF); // Puerto B como entrada


set_tris_c(0x00); // Puerto C como salida
set_tris_d(0x00); // Puerto D como salida

enable_interrupts(GLOBAL); // Se habilita la interrupcion global


enable_interrupts(INT_EXT); // Se habilita la interrupcion externa
ext_int_edge( H_TO_L ); // Habilita el flanco interrupción de H a L

write_eeprom(0,0); // Se almacena 0 en la memoria EEPROM, para que


// al prender el cicuito comience siempre en 0.
// Caso contrario comentarla para que siga contando.

cuenta =0x00; // Inicializa puerto B


Delay_1seg();

while(true)
{
//Resto del programa
}
}

Análisis del programa:

 El ejemplo básicamente es un contador ascendente de 0 a 99 que incrementa su valor cada


vez que se pulsa 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 se
45
quiere guardar un valor que estará comprendido entre 0 y 99, se utiliza el primer byte de la
memoria EEPROM. Para detectar cuando se pulsa el pulsador, se utiliza la interrupción
externa en el pin RB0/INT.

 La primera vez que se ejecute el programa el valor de la EEPROM es 0xFF (viene así de
fábrica) por lo que se tiene que sobrescribir su valor a 0x00 para iniciar la cuenta, después de
esto esta condición solo se cumplirá cuando el contador llegue a 99.

if ((read_eeprom(0)==0x99)||(read_eeprom(0)==0xFF))
{
write_eeprom(0,0); // Escribe el valor 0 en la dirección 0 de la
// memoria EEPROM.
cuenta=read_eeprom(0); // Asigna al puerto D el valor devuelto por la
// funcion de lectura de la EEPROM.
}

 Caso contrario si no cumple la condición anterior, se almacena en la memoria EPROM el


valor anterior incrementado en 1

else
{
write_eeprom(0,(read_eeprom(0))+1); // Escribe el valor leído de la
// memoria EPROM incrementado en 1 en la
// direccion 0 de la memoria EEPROM.
cuenta=read_eeprom(0);
}

En la función de escritura permite poner como parámetro la función de lectura, por lo que no
es necesario declarar una variable local para hacer la lectura y escritura a la vez.

 Para poder ver los registros de la EEPROM interna del PIC en Proteus, se selecciona Debug
--> PIC CPU EPROM Memory. Proteus simula bastante bien el uso de la memoria EEPROM
del PIC, si se para la simulación y se vuelve a iniciarla se ve como los valores de la
EEPROM se mantienen.

El circuito en Proteus es el siguiente:

46
R5
10k

RN1
1 16
2 15
Su Turno 3 14
4 13
5 12
6 11
7 10
C3 8 9

RX8
22pF U1
13 33
OSC1/CLKIN RB0/INT
X2 14
OSC2/CLKOUT RB1
34
CRYSTAL 35
RB2
2 36
C4 RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5
5 39
RA3/AN3/VREF+ RB6/PGC
22pF 6 40
RA4/T0CKI/C1OUT RB7/PGD
7
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
RC4/SDI/SDA
23 R3 R4
1 24 1k 1k
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
RD1/PSP1
20
R1 Q1 Q2
21 BC548C BC548C
RD2/PSP2
22 220
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
R2
RD6/PSP6
30 220
RD7/PSP7
PIC16F877A

47
17. Control de varia interrupciones

Ejemplo:

En este ejemplo se va a controlar varias interrupciones simultáneamente: la interrupción sobre la


cuenta del registro TMR0 y la interrupción al cambio de estado de las líneas RB4 a RB7. El PIC
a utilizar es el PIC16F877 a una frecuencia de oscilación de 4Mhz.

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.

Análisis del problema:

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


Interrupciones”, DBLINT.ASM.

El código en lenguaje C se muestra a continuación:

/******************************************************************************
* *
* DESCRIPCIÓN: Control de varias interrupciones *
* Varias_Interrupciones.c *
* *
******************************************************************************/

#include <16f877a.h>

#use delay(clock=4000000)
#fuses XT,NOWDT,NOPUT,NOPROTECT,NOLVP

#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


48
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:

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 este caso la funciones para los eventos: sobre flujo del
Timer0 y cambio de estado de las patitas RB3, … RB0, serían:

#int_RTCC
RTCC_isr()

#int_RB
RB_isr()

El circuito en Proteus es el siguiente:

49
A

D1
R2
300
LED-RED
C1
D2
R3
15p U1 300
13 33 LED-RED
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1 D3
X1 1
MCLR/Vpp/THV RB2
35
R4
CRYSTAL 36
RB3/PGM
2 37 300
C2 RA0/AN0 RB4
3 38 LED-RED
RA1/AN1 RB5
4 39
RA2/AN2/VREF- RB6/PGC
5 40
RA3/AN3/VREF+ RB7/PGD
15p 6
RA4/T0CKI
7 15
RA5/AN4/SS RC0/T1OSO/T1CKI
16
RC1/T1OSI/CCP2
8 17
RE0/AN5/RD RC2/CCP1
9
RE1/AN6/WR RC3/SCK/SCL
18 R6 R7 R8 R9
R1 10
RE2/AN7/CS RC4/SDI/SDA
23 300 300 300 300
330 24
RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877

50
18. Mostrar un mensaje por un LCD conectado al puerto B
Ejemplo:

En este programa muestra un mensaje por una LCD conectada en el puerto B. El PIC a utilizar es el
PIC16F877 a una frecuencia de oscilación de 4Mhz.

Análisis del problema:


La librería "lcdb.c" no viene en el CCS C compiler, por lo que se debe adjunta, para que sea copiada en
el directorio "PC/ Disco Local/ Archivos de Programa/ PICC/ Drivers/”, o también puede ser copiada en el
directorio actual del proyecto.

El código en lenguaje C se muestra a continuación:


#include <16F877.h> // PIC a utilizar
#include <lcdb.c> // Librería modificada para poder ser trabajada
// por el puerto B y sin la pata "RW" de la LCD.

#use delay(clock=4000000) // Cristal a utilizar

#fuses XT, NOWDT, NOPROTECT // Fusibles

////////////////////////////////////////////////////////////////////////////////////////////////////////////
// B2 -- E
// B3 ---RS
// B4 ---D4
// B5 ---D5
// B6 ---D6
// B7 ---D7
// (Sin 'RW') este pin del LCD se pone a tierra o masa.
///////////////////////////////////////////////////////////////////////////////////////////////////////////

void main(void)
{
lcd_init(); // inicializa el LCD
lcd_putc(" Mensaje_LCD"); // Escribe en la LCD
}

El circuito en Proteus es el siguiente:

51
LCD1
LM016L

U2
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
35
RB2
2 36
RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4

VDD
VSS

VEE
4 38

RW
RS

D0
D1
D2
D3
D4
D5
D6
D7
RA2/AN2/VREF-/CVREF RB5

E
5 39
RA3/AN3/VREF+ RB6/PGC
6 40
RA4/T0CKI/C1OUT RB7/PGD

1
2
3

4
5
6

7
8
9
10
11
12
13
14
7
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

52
19. Contador con LCD 2x16 conectada por puerto B
Ejemplo:

En este programa al pulsar a P1 se incrementará una cuenta de 0-255 y se mostrara por una pantalla
LCD 2x16, de igual manera al presionar a P2 se decrementará dicha cuenta. El PIC a utilizar es el
PIC16F877 a una frecuencia de oscilación de 4Mhz.

El código en lenguaje C se muestra a continuación:


#include <16f877.h> // PIC a utilizar
#include <lcdb.c> // Libreria para el manejo del LCD por el puerto B

#use delay(clock=4000000) // Cristal a utilizar

#fuses xt, nowdt, noprotect // Fusibles

#byte portb=0x6 // Declara el puerto B en su localidad

int A=0;

void main(void)
{
lcd_init(); // Inicia LCD
lcd_putc(" Contador\n"); // Muestra texto en el LCD
lcd_putc(" Digital"); // Muestra texto en el LCD
delay_ms(2000); // Retardo de 2 seg.
lcd_putc("\f"); // Limpia pantalla del LCD
lcd_putc(" Conteo\n"); // Muestra texto en el LCD

while(true)
{
if(input(pin_A0)==0) // Pregunta si RA0 es cero
{
A++; // Incrementa el conteo
delay_ms(500); // Para visualizar correctamente el conteo
if(A>=255) // Preguta si A es mayor o igual a 255,
{ // cuando sea igual iguala A a cero
A=0;
}
}
if(input(pin_A1)==0) // Pregunta si RA1 es cero
{
A--; // Decrementa el conteo
delay_ms(500); // Para visualizar mejor la cuenta
if(A==0) // Pregunta si A llego a cero, cuando sea
{ // 0 iguala A a 255
A=255;
}
}

lcd_gotoxy(2,2); // Ubica el cursor LCD en la fila 2 y columna 2


lcd_putc(" "); // Limpia ese sector de pantalla
printf(lcd_putc,"%3U",A); // Imprime el conteo por el LCD
}
}

El circuito en Proteus es el siguiente:


53
5V

LCD1
LM016L
P1
R1R2 U2
10k10k
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
35
P2 2
RB2
36
RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4

VDD
VSS

VEE
4 38

RW
RS

D0
D1
D2
D3
D4
D5
D6
D7
RA2/AN2/VREF-/CVREF RB5

E
5 39
RA3/AN3/VREF+ RB6/PGC
6 40
RA4/T0CKI/C1OUT RB7/PGD

1
2
3

4
5
6

7
8
9
10
11
12
13
14
7
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

54
20. Teclado 4x3 LCD

Ejemplo:

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 PIC a utilizar es el PIC16F877 a una
frecuencia de oscilación de 4Mhz.

Se adjunta en el programa las librerías:

 lcd.c: para el cambio de pines de la utilización del LCD.


 kbd_lib.c: para la utilización de teclados matriciales 4x3.

El código en lenguaje C se muestra a continuación:


/******************************************************************************
* *
* DESCRIPCIÓN: Teclado 4x3 y LCD 16x2 con el Pic 16F877A. *
* Teclado_4x3_LCD.c *
* *
******************************************************************************/

#include <16F877A.h>

#fuses XT,NOCPD

#use delay(clock=4M)

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

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

Análisis del código:

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

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

A continuación se asigna 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_d = 0x08

 Inicializar el teclado y el LCD:

lcd_init();
kbd_init();

 Se puede envíar mensajes al LCD, en este caso "\fPresione...\n", que limpia la pantalla y
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);

El circuito en Proteus es el siguiente:

56
1

3
A
1 2 3
B
4 5 6
C
7 8 9
U1
13 33
14
OSC1/CLKIN
OSC2/CLKOUT
RB0/INT
RB1
34
D
0 #
35
RB2
2 36
RA0/AN0 RB3/PGM
3 37
4
RA1/AN1 RB4
38
R1 LCD2
5
RA2/AN2/VREF-/CVREF RB5
39
R2 LM016L
6
RA3/AN3/VREF+ RB6/PGC
40
R3
10k
7
RA4/T0CKI/C1OUT RB7/PGD R4
10k
RA5/AN4/SS/C2OUT 10k
15 10k
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA

VDD
VSS

VEE
1 24

RW
RS

D0
D1
D2
D3
D4
D5
D6
D7
MCLR/Vpp/THV RC5/SDO

E
25
RC6/TX/CK
26
RC7/RX/DT

1
2
3

4
5
6

7
8
9
10
11
12
13
14
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

57
21. Teclado 4x4 y enviar el valor a una PC vía Rs232 (serial) y a un
display 7 segmentos

Ejemplo:

En este programa se ingresa una tecla desde un teclado 4x4 y se envía el valor a una PC vía RS232
(serial) y a un display 7 segmentos. El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de
4Mhz.

El código en lenguaje C es el siguiente:

/*----------------------------------------------------------------------------------------*\
| Ingreso tecla para enviar por la USART y a la PC. |
| |
\*----------------------------------------------------------------------------------------*/

#include <16F877.h>
#FUSES XT, NOPROTECT
#use delay (clock = 4000000)

#define use_portb_kbd TRUE // Para usar puerto B, caso contrario usa puerto D
#include "kbd_lib.c"

#use rs232(uart1, baud=9600) //usart1, configura XMIT y RCV para la USART 1

#byte puerto_D = 0x08

void Caracter_Display (char dato)


{
switch(dato) //visualiza el caracter recibido en el display
{
case '0':
puerto_D = 0x3F;
break;
case '1':
puerto_D = 0x06;
break;
case '2':
puerto_D = 0x5B;
break;
case '3':
puerto_D = 0x4F;
break;
case '4':
puerto_D = 0x66;
break;
case '5':
puerto_D = 0x6D;
break;
case '6':
puerto_D = 0x7D;
break;
case '7':
puerto_D = 0x07;
break;
case '8':
puerto_D = 0x7F;
58
break;
case '9':
puerto_D = 0x6F;
break;
case 'A':
puerto_D = 0xF7;
break;
case 'B':
puerto_D = 0x7C;
break;
case 'C':
puerto_D = 0x58;
break;
case 'D':
puerto_D = 0x5E;
break;
case 'E':
puerto_D = 0x79;
break;
case 'F':
puerto_D = 0x71;
break;
default:
break;
}
}

void main()
{
char c;
port_b_pullups(true); // Activa los pull ups del puerto B

set_tris_D( 0x00 ); // Puerto D como salida.

printf("Comunicacion serie.\r\n");

while(true)
{
c = kbd_getc();
if(c != 0 ) // Si se ha pulsado una tecla
{
putc(c); // Envia el caracter via serie por la USART del PIC
Caracter_Display (c);
}
}
}

Análisis del código:

 Se incluye una librería nueva:

#include "kbd_lib.c"

Esta librería es una modificación de "kbd.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 se ha hecho permite controlar un teclado de 4 x 4. Al ver el
código original es fácil hacer las modificaciones pertinentes para poder controlar un teclado
personalizado.
59
NOTA: La configuración de las teclas se debe realizar de acuerdo al teclado a usar.

 Se debe anteponer la siguiente sentencia de preprocesador a la sentencia #include


"kbd_lib.c":

#define use_portb_kbd TRUE // Para usar puerto B, caso contrario usa puerto D

Para usar el teclado elige el puerto B en lugar del puerto D, para usar el puerto D no se
necesita esta instrucción.

 Una forma más simple de seleccionar el valor decodificado para ser mostrado en el display
de 7 segmentos, que se utiliza en la sentencia switch(dato){}, es asignando los valores
decodificados en un vector como se muestra a continuación:

int valor_D = {0x3F,0x06,0x5B,0x4F,0x66, 0x6D, 0x7D, 0x07,


0x7F,0x6F,0xF7,0x7C,0x58,0x5E,0x79,0x71};

Luego se utiliza la sentencia:

puerto_D = valor_D[datos];

El circuito en Proteus es el siguiente:

60
A
1 2 3 A

U1
B
4 5 6 B
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
2
RB2
35
36
C
7 8 9 C
RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5
5
6
RA3/AN3/VREF+ RB6/PGC
39
40
D
0 # D
RA4/T0CKI/C1OUT RB7/PGD
7

4
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT RN1
19 1 16
RD0/PSP0
20 2 15
RD1/PSP1
21 3 14
RD2/PSP2
22 4 13
RD3/PSP3
27 5 12
RD4/PSP4
28 6 11
RD5/PSP5
29 7 10
RD6/PSP6
30 8 9
RD7/PSP7
PIC16F877A RX8

RXD

TXD

RTS

CTS

61
22. Comunicación serie entre dos PICs con la USART

La comunicación serie vía serie RS-232 tiene dos tipos: síncrona o 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 half duplex (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 tener la misma frecuencia, y 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 o 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 PC o para comunicar dos PIC's
entre sí, que es la más usada.

Ejemplo

En este ejemplo se va a comunicar dos PIC's entre sí vía serie RS-232 del tipo asíncrona,
haciendo uso del módulo USART (Universal Synchronous/Asynchronous Receiver Transmiter),
que incorporan la mayoría de los PIC’s de la gama media/alta. El PIC transmisor envía los
caracteres '1' y '2' y el PIC receptor recibe estos dos caracteres y almacena en la EEPROM. Por
último, se muestran los caracteres en la PC. El PIC a utilizar es el PIC16F877 a una frecuencia
de oscilación de 4Mhz.

El código en lenguaje C es el siguiente:

Al haber dos PIC’s se tendrá que hacer dos programas independientes, uno para el transmisor y
otro para el receptor.

Transmisor:

/*--------------------------------------------------------*\
| Comunicación entre dos PIC's por USART. Transmisor |
| |
| |
\*--------------------------------------------------------*/

#include <16F877.h>

#FUSES XT, NOPROTECT


#use delay(clock=4000000)
#use rs232(uart1, baud=9600)

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

62
Análisis del Código del Transmisor

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

#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 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.

Receptor:

/*--------------------------------------------------------*\
| Comunicación entre dos PIC's por USART. Receptor |
| |
| |
\*--------------------------------------------------------*/

#include <16F877.h>

#FUSES XT, NOPROTECT


#use delay(clock=4000000)
#use rs232(uart1, baud=9600)

// Declaración 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);

printf("%c %c", read_eeprom(0), read_eeprom(1));


}

Análisis del Código del Receptor

 El PIC receptor recibe los datos con la interrupción de la recepción de la comunicación serie
asincrónica RS232, por lo que tiene que activarse las interrupciones INT_RDA y GLOBAL.

 El primer dato recibe en la variable cdecenas y lo almacena en la dirección 0x00 de la


EEPROM, el segundo dato recibe en la variable cunidades y lo almacena en la dirección
0x01 de la EEPROM.

63
 Al final el programa receptor muestra los datos almacenados en la EEPROM en la PC.

El circuito en Proteus es el siguiente:

U1
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
35
RB2
2 36
RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5
5 39
RA3/AN3/VREF+ RB6/PGC
6 40
RA4/T0CKI/C1OUT RB7/PGD
7
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26 RXD
RC7/RX/DT
19 TXD
RD0/PSP0
20
RD1/PSP1
21 RTS
RD2/PSP2
22
RD3/PSP3
27 CTS
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

U2
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
35
RB2
2 36
RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5
5 39
RA3/AN3/VREF+ RB6/PGC
6 40
RA4/T0CKI/C1OUT RB7/PGD
7
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

64
23. Comunicación serie entre dos PICs con la USART y Teclado
Ejemplo:

En este ejemplo se va a comunicar dos PIC’s entre sí vía serie RS-232 del tipo asíncrona,
haciendo uso del módulo USART (Universal Synchronous/Asynchronous Receiver Transmiter),
que incorporan la mayoría de los PIC’s de la gama media/alta. El PIC U1 hará de PIC transmisor
de datos, que se encargará de comprobar si se pulsa alguna tecla en el teclado y en el caso de que
así sea, mandará el valor del caracter (1 byte) de la tecla pulsada, correspondiente al código del
teclado. El segundo PIC U2 implementará en su código una interrupción por recepción de datos
serie en la USART, que recibirá el carácter procedente de U1 y visualizará su valor en un display
de siete segmentos de cátodo común.

El código en lenguaje C es el siguiente:

Al haber dos PIC’s se tendrá que hacer dos programas independientes, uno para el transmisor y
otro para el receptor.

Transmisor:
/*-----------------------------------------------------------*\
| Comunicación entre dos PIC's por USART. Transmisor |
| |
\*-----------------------------------------------------------*/

#include <16F877.h>
#FUSES XT, NOPROTECT
#use delay (clock = 4000000)

#define use_portb_kbd TRUE // Para usar puerto el B, caso contrario usa puerto el D
#include "kbd_lib.c"

#use rs232(uart1, baud=9600)//usart1, configura XMIT y RCV para la USART 1

void main()
{
char c;
port_b_pullups(true); // Activa los pull ups del puerto B

printf("Comunicacion serie.\r\n");

while(true)
{
c = kbd_getc();
if(c != 0 ) //si se ha pulsado una tecla
{
putc(c); //Envia el caracter via serie por la USART del PIC
}
}
}

Análisis del Código del Transmisor

 Se incluye una librería nueva:

#include "kbd_lib.c"

65
Esta librería es una modificación de "kbd.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 se ha hecho permite controlar un teclado de 4 x 4. Al ver el
código original es fácil hacer las modificaciones pertinentes para poder controlar un teclado
personalizado.

NOTA: La configuración de las teclas se debe realizar de acuerdo al teclado a usar.

 Se debe anteponer la siguiente sentencia de preprocesador a la sentencia #include


"kbd_lib.c":

#define use_portb_kbd TRUE // Para usar puerto B, caso contrario usa puerto D

Para usar el teclado elige el puerto B en lugar del puerto D, para usar el puerto D no se
necesita esta instrucción.

 Solo son necesarias dos funciones para utilizar la librería "kbd_lib.c" 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, que muestra un
comentario en el encabezado de esta librería, indicando, entre otras cosas, que se puede 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.

Entonces, para que funcione el ejemplo al completo se tiene que modificar la librería para
que sea capaz de controlar el teclado 4x4 o modificar el circuito para tener un teclado de 4x3.

 Se debe usar la siguiente sentencia de preprocesador al usar el puerto:

port_b_pullups(true); // Activa los pull ups del puerto B

Activa las resistencias de pull ups del puerto B

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

#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 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 función putc().

Receptor:

66
/*--------------------------------------------------------*\
| Comunicación entre dos PIC's por USART. Receptor |
| |
| |
\*--------------------------------------------------------*/

#include <16F877.h>
#FUSES XT, NOPROTECT
#use delay (clock = 4000000)
#byte puerto_D = 0x08
#use rs232(uart1, baud=9600)
char dato;

#int_rda
void rd_isr(void) // Funcion de interrupcion por recepción de datos USART
{
dato= getc();

switch(dato) // Visualiza el caracter recibido en el display


{
case '0':
puerto_D = 0x3F;
break;
case '1':
puerto_D = 0x06;
break;
case '2':
puerto_D = 0x5B;
break;
case '3':
puerto_D = 0x4F;
break;
case '4':
puerto_D = 0x66;
break;
case '5':
puerto_D = 0x6D;
break;
case '6':
puerto_D = 0x7D;
break;
case '7':
puerto_D = 0x07;
break;
case '8':
puerto_D = 0x7F;
break;
case '9':
puerto_D = 0x6F;
break;
case 'A':
puerto_D = 0xF7;
break;
case 'B':
puerto_D = 0x7C;
break;
case 'C':
puerto_D = 0x58;
break;
case 'D':
puerto_D = 0x5E;
break;
case 'E':
puerto_D = 0x79;
break;
case 'F':
puerto_D = 0x71;
break;

67
default:
break;
}
}

void main()
{
enable_interrupts(global);//Habilita interrupción USART
enable_interrupts(int_rda);

set_tris_d( 0x00 ); // Puerto D como salida.


puerto_D = 0x00; //inicializa puerto D

while(true){
//Código principal
}
}

Análisis del Código del Receptor

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

Cuando se produce la interrupción se obtiene el caracter 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.

 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 el pin
de transmisión de U2 con la de recepción de U1 se podría suprimir en el circuito.

 Una forma más simple de seleccionar el valor decodificado para ser mostrado en el display
de 7 segmentos, que se utiliza en la sentencia switch(dato){}, es asignando los valores
decodificados en un vector como se muestra a continuación:

Int valor_D = {0x3F,0x06,0x5B,0x4F,0x66, 0x6D, 0x7D, 0x07,


0x7F,0x6F,0xF7,0x7C,0x58,0x5E,0x79,0x71};

Luego se realiza:

puerto_D = valor_D[datos];

El circuito en Proteus es el siguiente:

68
U1
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
35
RB2
2 36
3
RA0/AN0
RA1/AN1
RB3/PGM
RB4
37
A
7 8 9
4 38
RA2/AN2/VREF-/CVREF RB5
5 39
RA3/AN3/VREF+ RB6/PGC
6 40
7
RA4/T0CKI/C1OUT
RA5/AN4/SS/C2OUT
RB7/PGD B
4 5 6
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
10
RE1/AN6/WR
RE2/AN7/CS
RC2/CCP1
RC3/SCK/SCL
18
C
1 2 3
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO ON
RC6/TX/CK
RC7/RX/DT
25
26
D
C 0 = +

4
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877A

U2
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
35
RB2
2 36
RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5
5 39
RA3/AN3/VREF+ RB6/PGC
6 40
RA4/T0CKI/C1OUT RB7/PGD
7
RA5/AN4/SS/C2OUT
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA
1 24
MCLR/Vpp/THV RC5/SDO
25
RC6/TX/CK
26
RC7/RX/DT RN1
19 1 16
RD0/PSP0
20 2 15
RD1/PSP1
21 3 14
RD2/PSP2
22 4 13
RD3/PSP3
27 5 12
RD4/PSP4
28 6 11
RD5/PSP5
29 7 10
RD6/PSP6
30 8 9
RD7/PSP7
PIC16F877A RX8

69
24. Medir temperatura con LM35 y mostrarla por LCD 2x16

Ejemplo:

En este programa sensa la temperatura con un LM35 y muestra por una LCD. El PIC a utilizar es el
PIC16F877 a una frecuencia de oscilación de 4Mhz.

El código en lenguaje C es el siguiente:

#include "16f877.h"

#device adc=10 // Usa una resolucion de 10 bits


#use delay(clock=4000000)

#fuses xt, nowdt, noprotect

#include "lcd.c"
#use standard_io (D) //Selecciona el puerto D para el LCD

void main (void)


{
float temper, medicion;

lcd_init(); // Inicia LCD


lcd_putc(" Termometro\n"); // Muestra el texto en el LCD
lcd_putc(" con LM35"); // Muestra el texto en el LCD
delay_ms(2000);
lcd_putc("\f"); // Limpia pantalla del LCD
lcd_putc("Temperatura\n"); // Muestra el texto en el LCD
lcd_putc("actual"); // Muestra el texto en el LCD
delay_ms(1000);
lcd_gotoxy(14,2); // Ubica el cursor LCD en la columna 14 y fila 2
lcd_putc("oC");

while (TRUE)
{
lcd_gotoxy(8,2); // Ubica el cursor LCD en la columna 8 y fila 2
lcd_putc(" "); // Limpia ese sector de la pantalla del LCD
lcd_gotoxy(8,2); // Ubica el cursor LCD en la columna 14 y fila 2

setup_adc (adc_clock_internal);
setup_adc_ports (all_analog);
set_adc_channel (0); // Elige canal RA0
delay_us (20);
medicion=read_adc (); // Hace conversión AD
setup_adc (adc_off); // Apaga ADC
temper=medicion*(0.48875); // Pasa binario a °C

printf(lcd_putc,"%02.1f",temper); // Muestra en la forma xx.x °C


delay_ms (1000);
}
}

Análisi del código:

El valor 0.48875 se obtiene de dividir 5/1023 y el resultado se multiplicarlo por 100:

0.48875 = (5/1023) *100


70
donde:
 5, corresponde a los 5 voltios aplicado a el voltaje de referencia,
 1023, corresponde a los 10 bit de resolución del conversor análogo digital que se selecciona.

Si se utiliza la resolución de 8 bits del conversor análogo digital, donde el número máximo posible es de
255, la fórmula sería:

1.96 = (5/255)*100

El circuito en Proteus es el siguiente:

C1

22p U1
13 33 LCD1
OSC1/CLKIN RB0/INT LM032L
X1 14
OSC2/CLKOUT RB1
34
CRYSTAL 1 35
C2 MCLR/Vpp/THV RB2
36
RB3/PGM
2 37
RA0/AN0 RB4
3 38
RA1/AN1 RB5
22p 4 39
RA2/AN2/VREF- RB6/PGC
5 40
RA3/AN3/VREF+ RB7/PGD

VDD
VSS

VEE
5v 6

RW
RS

D0
D1
D2
D3
D4
D5
D6
D7
RA4/T0CKI

E
5v 7 15
RA5/AN4/SS RC0/T1OSO/T1CKI
16
RC1/T1OSI/CCP2

1
2
3

4
5
6

7
8
9
10
11
12
13
14
8 17
U3 RE0/AN5/RD RC2/CCP1
1 9 18
RE1/AN6/WR RC3/SCK/SCL
10 23
RE2/AN7/CS RC4/SDI/SDA
24
RC5/SDO
25
67.0 RC6/TX/CK
26
RC7/RX/DT
2 19
VOUT RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
3 LM35 27
RD4/PSP4
28
RD5/PSP5
29
RD6/PSP6
30
RD7/PSP7
PIC16F877

71
25. Mandar un mensaje por Rs232 y mostrarlo por un LCD 2x16

Ejemplo:

En este programa envía un mensaje por el puerto Rs232 de la PC al PIC y muestra dicho mensaje por
una LCD 2x16. El PIC a utilizar es el PIC16F877 a una frecuencia de oscilación de 4Mhz.

El código en lenguaje C es el siguiente:

// comunicacion RS232 con PIC

#include <16F877A.h>

#fuses HS, NOWDT, NOPROTECT

#use delay(clock=4000000)

#use rs232(baud=9600,xmit=pin_c6,rcv=pin_c7)
// Configura el puerto serie a una velocidad de 9600 baudios
#include <lcd.c> // Libreria para el manejo del LCD
char dato_recibido[16]; // Variable para recibir los datos del RS232

#int_rda // Interrupcion por recibir los datos del RS232


void rda_isr()
{
gets(dato_recibido); // Almacena en la variable dato_recibido los
// datos que se esten enviando desde la PC, hasta digitar
// la tecla ENTER
}

void main()
{
enable_interrupts(int_rda); // Se habilita la interrupcion por escritura del puerto serial
enable_interrupts(global);

set_tris_d(0b00000000);

lcd_init();
lcd_gotoxy(1,1);
lcd_putc(" com RS232 ");

while(TRUE)
{
lcd_gotoxy(1,2);
printf(lcd_putc,"%s",dato_recibido); // Se envia al LCD lo que se recibió de la PC
}
}

El circuito en Proteus es el siguiente:

72
U1
13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
35
RB2 RXD
2 36
RA0/AN0 RB3/PGM
3 37
RA1/AN1 RB4 TXD
4 38
RA2/AN2/VREF-/CVREF RB5
5 39
RA3/AN3/VREF+ RB6/PGC RTS
6 40
RA4/T0CKI/C1OUT RB7/PGD
7
RA5/AN4/SS/C2OUT CTS
15
RC0/T1OSO/T1CKI
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
23
RC4/SDI/SDA LCD1
1 24
MCLR/Vpp/THV RC5/SDO LM016L
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4

VDD
VEE

VSS
28

RW
RS
D7
D6
D5
D4
D3
D2
D1
D0
RD5/PSP5

E
29
RD6/PSP6
30
RD7/PSP7

14
13
12
11
10
9
8
7

6
5
4

3
2
1
PIC16F877A

73
26. Medir temperatura con LM35 y mandar el valor vía RS232 (serial)

Ejemplo:

En este programa sensa la temperatura con un LM35 y envía el valor a una PC vía Rs232 (serial). El PIC
a utilizar es el PIC16F877 a una frecuencia de oscilación de 4Mhz.

El código en lenguaje C es el siguiente:

#include <16f877A.h> // PIC a utilizar

#device adc=10 // Conversor ADC de 10 bits


#fuses XT, NOWDT // Fusibles

#use delay (clock=4000000) // Fosc=4Mhz


#use rs232(baud=9600,xmit=pin_c6,rcv=pin_c7, bits=8, parity=N) //Configuracion del RS232

#include<lcd.c> // Libreria para el manejo del LCD

void main(void)
{
float medicion, temper;

setup_adc_ports(AN0);
setup_adc(ADC_CLOCK_INTERNAL);
lcd_init();

while(true)
{
set_adc_channel (0);
delay_us (20);
medicion=read_adc ();

temper=(medicion*(0.48875));
printf(lcd_putc, "\f Voltage = %02.1fV", temper);
printf("Voltage = %01.2fV\r", temper);
delay_ms(50);
}
}

El circuito en Proteus es el siguiente:

74
U1
U2(+VS) 13 33
OSC1/CLKIN RB0/INT
14 34
OSC2/CLKOUT RB1
35
U2 RB2
1 2 36
RA0/AN0 RB3/PGM RXD
3 37
RA1/AN1 RB4
4 38
RA2/AN2/VREF-/CVREF RB5 TXD
5 39
44.0 RA3/AN3/VREF+ RB6/PGC
6 40
RA4/T0CKI/C1OUT RB7/PGD RTS
7
RA5/AN4/SS/C2OUT
2 15
VOUT RC0/T1OSO/T1CKI CTS
8 16
RE0/AN5/RD RC1/T1OSI/CCP2
9 17
RE1/AN6/WR RC2/CCP1
10 18
RE2/AN7/CS RC3/SCK/SCL
3 LM35 23
RC4/SDI/SDA LCD1
1 24
MCLR/Vpp/THV RC5/SDO LM016L
25
RC6/TX/CK
26
RC7/RX/DT
19
RD0/PSP0
20
RD1/PSP1
21
RD2/PSP2
22
RD3/PSP3
27
RD4/PSP4

VDD
VEE

VSS
28

RW
RS
D7
D6
D5
D4
D3
D2
D1
D0
RD5/PSP5

E
29
RD6/PSP6
30
RD7/PSP7

14
13
12
11
10
9
8
7

6
5
4

3
2
1
PIC16F877A

75
BIBLIOGRAFÍA

[1] AquíHayApuntes.com, "Índice Prácticas PIC en C", URL: http://www.aquihayapuntes.com/indice-


practicas-pic-en-c.html, accedido el 2 de abril de 2016.

[1] uControl, "Programas PIC en C", URL: uControl.com.ar, accedido el 21 de octubre de 2016.

76

También podría gustarte