Está en la página 1de 12

Sistemas Electrónicos Digitales

Práctica 3.
Título: Programación de microcontrolador PIC. Temporizadores e
interrupciones.

Objetivo: Utilizando el programa Proteus y el compilador CCS, ampliar conocimientos


de programación de temporizadores y contadores en microcontroladores PIC (modelos
16F84 y 16F876). Programación de interrupciones y combinar interrupciones de
temporizadores y de interrupciones externas.

Desarrollo:

Esta práctica está dividida en 5 apartados:

I) Uso del temporizador TIMER0 del PIC16F84 en modo contador de pulsos externos.

II) TIMER0 del PIC16F84 en modo temporizador con interrupciones.

III) Uso de un temporizador de mayor tamaño para la medida de tiempos:


Temporizador TIMER1 de un PIC16F876

IV) Control del multiplexado en el tiempo del display mediante interrupciones del
temporizador TIMER0.

V) Diseño de un frecuencímetro digital combinando interrupciones de temporizador e


interrupciones externas INT_EXT (pin RB0 del PIC).

Recordamos el diagrama del temporizador programable TIMER0 del PIC16F84:

El desbordamiento del temporizador ocurre transcurrido el tiempo:


T = 4 · Tosc · TMR0 · Divisor

Si el oscilador trabaja a 4 MHz el contador se desborda cada 256 cuentas:


T = 4 · 0.25 µS · 256 · Divisor

Como el registro contador TMR0 es de 8 bits, se desborda cada 256 pulsos recibidos en
PSout. El prescaler permite dividir el número de pulsos por un factor: 2, 4, 8, 16, 32,
64, 128, o 256; multiplicando en ese factor el número de pulsos de la entrada primaria
necesarios para desbordarlo. Esta entrada de pulsos puede ser de procedencia interna
(un pulso por cada ciclo de instrucción), o externa (pulsos recibidos en la patilla
RA4/T0CKI).

SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.1


Programación con funciones del CCS:

Configuración de TIMER0 (también denominado RTCC):


SETUP_TIMER_0 (parámetros); configura el temporizador. Este temporizador también
puede configurarse con SETUP_COUNTERS();
 ver lista de parámetros en archivo 16F64.h
En general, SETUP_TIMER_x() permite configurar el temporizador x que esté
disponible en el PIC (x = 0, 1, 2,…)

Funciones para manejo de temporizadores:


SET_TIMER_x(valor); esta función fija el valor del temporizador x, actualizando el
correspondiente registro TMRx. El argumento valor será de tipo int8 o in16,
dependiendo del tamaño del temporizador. SET_TIMER_0() es equivalente a
SET_RTCC()
GET_TIMER_x(); lee el valor del temporizador x (devuelve un int8 o un int16 según el
tamaño del temprizador).

I) En primer lugar, partiendo del esquemático de la práctica de la anterior, usamos el


temporizador interno del PIC (el TIMER0) en el ‘modo contador de pulsos externos’,
para contar el número de pulsos que se reciben en cada segundo por la entrada
RA4/T0CKI, y visualizaremos el resultado en los dos dígitos de 7segmentos.

Inicialmente mantenemos en el diseño la frecuencia de reloj: Fosc=l Mhz. El valor de


esta frecuencia no debe afectar en este caso.
Conectamos a la entrada RA4/T0CKI un generador de pulsos, cuya frecuencia podamos
variar (instrumento virtual ‘SIGNAL GENERATOR’ de Proteus). Hay que ajustar su
salida para que los pulsos sean entre 0V y 5V). Con el osciloscopio virtual podemos
observar la señal de entrada de pulsos, y también vamos a visualizar la salida RA3, que
haremos cambiar cada vez que el PIC haya contabilizado 1 segundo.

SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.2


EJERCICIO 3.1
Partiendo del programa del segundero (ejercicio 2.3 de la práctica anterior), la variable
que utilizábamos para representar e incrementar los segundos (denominada count en
este ejemplo), la usamos ahora para leer el número de pulsos externos recibidos en un
segundo. Para ello, tras declarar variables, en lugar de incrementar count, podemos usar
el siguiente código:
…..
//Configuramos el TIMER0 para que funcione como CONTADOR de pulsos EXTERNOS (con
//T0_EXT_L_TO_H) y sin pre-scaler (con T0_DIV_1):
setup_timer_0(T0_EXT_L_TO_H | T0_DIV_1); ///es igual poner RTCC en lugar de T0.
while (TRUE) {
while ( input(PIN_A0) ) {
count=get_timer0(); //en este ejemplo 'count' es el número de pulsos recibidos, deberá ser de tipo int8
set_timer0(0);//se reinicia la cuenta poniendo el registro TMR0=0.
output_toggle(PIN_A3); //para ver con el osciloscopio cada cuánto tiempo estamos realmente midiendo.
//A continuación ponemos el bucle de visualización, en BCD, en los dos dígitos de 7-segmentos,
//visualizando en este caso el número de pulsos recibidos (variable count).
// La duración del bucle debe ser de 1 segundo.
…..

Compilamos y simulamos, haciendo variar la frecuencia de impulsos del generador


entre 0 y 99Hz.
Observa el error de medida e intenta reducirlo modificando el argumento de las
llamadas a delay_ms() o delay_us() que hayas utilizado en el bucle de visualización.
• Si incrementamos la frecuencia de reloj del PIC, por ejemplo a 4MHz, ¿se observa
algún cambio en el funcionamiento del sistema? Recuerda que esta frecuencia de
reloj hay que fijarla tanto en el programa en C (directiva #USE DELAY) como en las
propiedades del PIC (en Proteus).
• Ahora aumentamos el valor del pre-scaler. Con el parámetro T0_DIV_2
observaremos que los valores leídos corresponden a la mitad del valor real.
• Si ponemos el pre-scaler a 256; calcula qué frecuencia real de los pulsos de entrada
correspondería al tope del rango de medida que podemos visualizar (99 en este
diseño).
• Variando la frecuencia de entrada, comprueba que los valores que puedes medir
experimentalmente (hasta que la simulación deja de seguir el tiempo real) se
aproximan a los correctos (según tus cálculos).

II) Partiendo de nuevo del diseño del segundero de la Práctica 2, lo implementamos ahora
realizando la cuenta de tiempo mediante interrupciones del temporizador interno del
PIC16F84 (en lugar de usar la función delay).
Incluso con una frecuencia de reloj baja (ponemos de nuevo 1MHz), con este
temporizador de 8 bits no se puede alcanzar un segundo sin desbordamiento, ya que el
tiempo máximo que podría contabilizar es:
Tmax = 4 · 1 µS · 256 · 256 ≈ 262,1ms

Lo que haremos en este apartado es configurar el temporizador para que se desborde


cada décima de segundo, de manera que transcurridos 10 desbordamientos
contabilizaremos un segundo. Para ello, cada vez que se desborde TMR0 deberemos
recargarlo con un valor Tini que cumpla:

T = 4 · 1 µS · (256 – Tini)· Prescaler = 100ms


Es decir, para un Prescaler=128 obtenemos Tini ≈ 61.
SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.3
EJERCICIO 3.2

Partiendo del programa del segundero (ejercicio 2.3 de la práctica anterior),


introducimos los siguientes cambios:

a) Antes de definir main(), definimos la rutina de interrupción (ver ANEXO al final de


este documento), añadiendo previamente la declaración de las variables count y
desbordamientos para que sean globales:

….
int count; //lleva la cuenta de los segundos; definida como variable global.
int desbordamientos;// número de desbordamientos de TMR0.
….

#INT_TIMER0
void decimas_segundo(void) {
set_timer0(61); ///recargamos el TMR0 para el siguiente desbordamiento
//de manera que TMR0 se desborde cada 100ms aproximadamente
if (++desbordamientos==10){ //se incrementa, y si ha pasado un segundo (10x100ms):
desbordamientos=0;
if (++count==60) count =0 ;//se incrementa y se reinicia count si han pasado 60 segundos
}
}

b) En el main(), antes del bucle de refresco del display, configuramos el


temporizador y las interrupciones:
….
setup_timer_0(RTCC_INTERNAL | RTCC_DIV_128); //configura el TIMER0 para que
////se incremente cada 128 ciclos de instruccion
enable_interrupts(INT_TIMER0);
clear_interrupt(INT_TIMERO): //borra el flag T0IF (no es imprescindible en este ejemplo)
enable_interrupts(GLOBAL);
….

NOTA: como ahora el main() no es quien lleva la cuenta del tiempo, sino que se hace
en la rutina de interrupción, debemos eliminar del main el incremento de la variable
contador de segundos que utilizábamos para ello en el ejercicio 2.3.

• Compila el programa y comprueba mediante simulación si se ha conseguido un


funcionamiento correcto del segundero.

SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.4


III)Para medir el tiempo, siempre que sea posible es preferible hacerlo con un solo
recorrido del temporizador, en lugar de con múltiples desbordamientos. Esto facilita una
medida más precisa.
En este apartado sustituimos el microcontrolador 16F84 por otro de la misma gama pero
que incorpora más recursos: el PIC16F876. En particular, este micro dispone de tres
temporizadores TIMER0, TIMER1 y TIMER2, siendo su TIMER1 de 16 bits:

Al igual que el TIMER0, puede funcionar como contador de pulsos externos o bien
internos, y dispone de un pre-scaler, aunque éste sólo puede dividir por 1, 2, 4 u 8.
En primer lugar, vamos a sustituir en el diseño del segundero el PIC16F84 por el
PIC16F876, y vamos a utilizar una interrupción de TIMER1, configurado para que se
desborde cada segundo.

EJERCICIO 3.3

En el programa del segundero con interrupción por TIMER0 (del ejercicio 3.2)
introducimos los siguientes cambios para utilizar en su lugar el TIMER1 (suprimiendo
las líneas de código que hacían referencia al TIMER0):

SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.5


#include <16F876.h> ///en lugar de la referencia al 16F84
….
#INT_TIMER1
void un_segundo(void) {
set_timer1(3036); ///recargamos el TMR1 para el siguiente desbordamiento
if (++count==60) count =0 ;//se reinicia count si han pasado 60 segundos
}
….

En el main(), antes del bucle de refresco del display:


….
setup_timer_1(T1_INTERNAL | T1_DIV_BY_4); //para que se incremente
////cada 4 ciclos de instruccion
enable_interrupts(INT_TIMER1);
clear_interrupt(INT_TIMER1);
enable_interrupts(GLOBAL);
….

• Compila el programa y comprueba el correcto funcionamiento del segundero. En este


caso sólo se está produciendo una interrupción cada segundo, en lugar de las 10
interrupciones que tenían que producirse con el temporizador de 8 bits. Comprueba en
qué instantes se interrumpe la CPU introduciendo un punto de parada dentro de la rutina
de interrupción. ¿Qué diferencia de tiempo mides con Proteus entre dos paradas
consecutivas?
• Con el pre-scaler utilizado, calcula el tiempo (expresado en microsegundos) que tarda
TIMER1 en desbordarse desde que lo recargamos.

IV) En los apartados anteriores la función de visualización multiplexada en los dígitos de 7


segmentos la sigue realizando el programa principal, de manera que difícilmente podría
atender otra tarea, ya que el refresco de los dígitos requiere de una temporización que
hay que aplicar continuamente. En este apartado utilizamos el TIMER0 para controlar el
display mediante interrupciones, a la vez que el TIMER1 implementa la cuenta del
tiempo para el segundero, también con interrupciones.
Utilizamos el mismo esquemático de Proteus del apartado III, con un PIC16F876.

EJERCICIO 3.4:
En el programa del segundero con interrupción por TIMER1 (del ejercicio 3.3)
introducimos los siguientes cambios para que el refresco del display se realice con
interrupciones del TIMER0 (suprimiendo las líneas de código del main que se
encargaban de esta función):
SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.6
……
#int_TIMER0
void refresca_display(void) {
set_timer0(100); //con prescaler=16 y carga TMR0inicial= 100, T_desborda ~ 10ms
///Utiliza el código que sigue o adapta el que tuvieras previamente
///para controlar el display de 2 dígitos (en este código, la variable display deberá haberse
//declarado como short o int1):
if ( input(PIN_A0) ){
if(display){
output_low(PIN_A2); //apaga dígito de unidades
output_b(~tabla[count/10]); //complementado por ser ánodo común
output_high(PIN_A1);//enciende dígito de decenas
}else{
output_low(PIN_A1); //apaga dígito de decenas
output_b(~tabla[count%10]);
output_high(PIN_A2);//enciende dígito de unidades
}
display=!display;
}else{
count=0;
output_b(~tabla[count]);
output_high(PIN_A1);
output_high(PIN_A2);
}
}
……

Ahora, el programa principal sólo tiene que configurar los temporizadores y las
interrupciones, y el resto queda desocupado (podría ejecutar otras tareas):

……
void main() {
//Configuramos TIMER0 y TIMER1, y habilitamos interrupciones:
setup_timer_0(T0_INTERNAL | T0_DIV_16); //prescaler=16
setup_timer_1(T1_INTERNAL | T1_DIV_BY_4); //prescaler=4
enable_interrupts(INT_TIMER0);
enable_interrupts(INT_TIMER1);
clear_interrupt(INT_TIMER0);
clear_interrupt(INT_TIMER1);
enable_interrupts(GLOBAL);

while (TRUE) {}
}

• Calcula cada cuánto tiempo estamos ahora refrescando cada dígito del display, y mide
con el osciloscopio su valor al simular con Proteus.
• En modo debug, introduce de nuevo un punto de parada en la interrupción del TIMER1
para comprobar si las interrupciones frecuentes de TIMER0 pueden estar afectando a la
precisión del segundero.

SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.7


V) En este apartado vamos a combinar interrupciones de un temporizador y de una fuente
de interrupción externa, planteando el diseño de un frecuencímetro digital para medidas
de señales de pulsos de baja frecuencia.
En primer lugar programamos una tarea sencilla para familiarizarnos con las
interrupciones externas por flanco en las líneas menos significativas del puerto B. Tanto
el PIC16F84 como el PIC16F876 sólo disponen de este tipo de interrupción
(denominada EXT) en la entrada RB0. Otros modelos más avanzados disponen de
varias (EXT, EXT1, EXT2…) en pines RB0, RB1, RB2...

EJERCICIO 3.5.1:
Partiendo del mismo esquemático utilizado en el ejercicio 3.3 o el 3.4, vamos a ejecutar
el siguiente programa, que simplemente hace cambiar el pin RA3 de valor cada vez que
llega un flanco ascendente de la señal de entrada en RB0:
#include <16F876.h>
#int_EXT //definimos interrupcion por flanco en el pin RB0
void llega_flanco(void) {
output_toggle(PIN_A3);
}
void main(void) {
//Configuramos y habilitamos interrupciones:
ext_int_edge(0, L_TO_H); //configura la interrupción externa en RB0 para flanco ascendente
enable_interrupts(INT_EXT); //permiso de la interrupción externa
clear_interrupt(INT_EXT); //aconsejable borrar flag por si se ha activado con anterioridad.
enable_interrupts(GLOBAL); // permiso global: GIE=1
while(true){}
}

• Conecta una fuente de señal cuadrada (en instrumentos virtuales: GENERATORS >
DCLOCK) a la entrada RB0, y observa con el osciloscopio las señales en los pines RB0
y RA3. Fijando una frecuencia de entrada de 1KHz (por ejemplo), y con una frecuencia
de reloj del micro de 1MHz, mide el retraso entre la llegada del flanco en RB0 y el
cambio en RA3.
• En el programa en ensamblador generado por el compilador, intenta identificar el
bloque de sentencias correspondiente a la rutina de interrupción, y observa la cantidad
de código extra introducido por el compilador (desde la posición 0004 de la memoria de
programa hasta la sentencia ensamblador RETFIE). Aumenta la frecuencia de reloj del
micro a 10MHz (en CCS y en Proteus) y compara el retraso de la señal generada con el
que se tenía para un reloj de 1MHz.
• No obstante, a pesar de los retrasos, observa que el tiempo en alta o en baja de la señal
de salida coincide con el periodo de la señal de entrada.
• Sustituye ahora la rutina de interrupción por esta otra:
short subida; //declarada como global
#int_EXT
void llega_flanco(void) {
output_toggle(PIN_A3);
if (subida) ext_int_edge(H_to_L); //configura para que la siguiente sea por flanco descendente
else ext_int_edge(L_to_H); //o bien para que la siguiente sea por flanco ascendente
subida=!subida;
}

● Con la ayuda del osciloscopio, explica el comportamiento observado ahora, en


comparación con el que obteníamos con el programa anterior.

SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.8


EJERCICIO 3.5.2: Frecuencímetro digital.
Procedemos ahora con la implementación de un frecuencímetro digital adecuado para
señales de entrada de baja frecuencia, que mida hasta 99.99Hz, y desde un valor lo más
pequeño posible.

Comenzamos sustituyendo el display de 2 dígitos por uno de 4, para poder visualizar las
frecuencias de entrada con dos decimales (componente de simulación: 7SEG-
MPX4_CA de ánodo común, o el 7SEG-MPX4_CC si se prefiere de cátodo común).
Dado que la entrada de interrupción es RB0, esta línea la dejamos libre para captar por
interrupciones los pulsos de la señal a medir. Una posibilidad es utilizar las conexiones
que se indican a continuación, empleando el Port_C para proporcionar los valores de
los segmentos del display, y cuatro líneas del Port_A del PIC16F876 para los
ánodos/cátodos comunes de los dígitos. Observa que se ha conectado también la entrada
para encendido del punto decimal.

NOTA: Los displays de 2 o de 4 dígitos que estamos utilizando son modelos para
simulación. Un display real requeriría activar los ánodos/cátodos comunes mediante
elementos que amplificaran la corriente en esos terminales (por ejemplo transistores), ya
que las salidas del PIC no son capaces de dar/extraer la corriente necesaria.

En primer lugar implementamos el frecuencímetro utilizando la interrupción externa


por flancos de subida en la entrada RB0, y el TIMER1 para medir el tiempo entre
flancos de subida consecutivos; dejando al programa principal la tarea de refresco del
display.
La frecuencia de pulsos de la señal de entrada en RB0 se calcula como el inverso del
tiempo entre dos flancos de subida consecutivos (el periodo) de dicha señal.

Para conseguir que el TIMER1 permita medidas de tiempos largos, fijamos su prescaler
al máximo valor (8 en este TIMER). Si Fosc=1MHz, permite medir hasta 4*65536*8µs.
Es decir podríamos medir una frecuencia mínima del orden de 0.5Hz.

SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.9


• Compila el siguiente programa y simula con Proteus, haciendo variar la frecuencia de
entrada entre 0,5Hz y 99,99Hz y observando la precisión que se obtiene. Esta precisión
disminuye al aumentar la frecuencia; indica posibles causas.

#include <16F876.h>
#use delay(clock=1000000) //reloj de 1MHz
int16 tiempo;
int16 count; //count es ahora la FRECUENCIA de pulsos a visualizar
int display; //indica el dígito a visualizar
const int tabla[10]={0x3f,0x6,0x5b,0x4f,0x66,0x6d,0x7d,0x7,0x7f,0x6f};
const int16 factor[4]={1000,100,10,1};

#int_EXT
void llega_pulso(void) {
tiempo=get_timer1(); //lee el tiempo transcurrido desde el flanco ascendente previo (el periodo)
set_timer1(0); //reinicia para nuevo pulso
}

void main() {
//Configuramos temporizadores e interrupciones:
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);//prescaler maximo para medir frecuencias bajas
ext_int_edge(L_TO_H); //por defecto ya es así
enable_interrupts(INT_EXT);
clear_interrupt(INT_EXT);
enable_interrupts(GLOBAL);

while (TRUE) {
count=100000000/((int32)tiempo * 32); //representa la frecuencia en Hz*100
///en donde frecuencia es el inverso de (4*Tosc*prescaler*tiempo)
output_A(0); //apaga todos los dígitos
output_c(~tabla[count/factor[display]%10]); //pone los valores de los segmentos
switch(display) { /////recorre los cuatro dígitos
case 0: output_high(PIN_A0); //enciende decenas
break;
case 1: {output_high(PIN_A1); //enciende unidades y punto decimal
output_low(PIN_C7);
} //fin de case 1
break;
case 2: output_high(PIN_A2);
break;
case 3: output_high(PIN_A3);
break;
} //////fin del switch

if(++display==4) display=0;
delay_ms(5); /////tiempo que se mantiene encendido cada dígito
output_high(PIN_C7); ////apaga el punto decimal
} ////fin de while
}

NOTA: Alternativamente, podríamos implementar el frecuencímetro contando el


número de pulsos recibidos en un intervalo de tiempo dado, por ejemplo en un segundo,
tal como hicimos en el ejercicio 3.1. Para ello utilizaríamos (en lugar de la interrupción
INT_EXT) un TIMER funcionando en modo contador de pulsos externos, y otro
temporizador podría (mediante interrupciones) fijar el intervalo de tiempo de cuenta.
Esta solución da mejores resultados que la basada en interrupciones externas cuando
aumenta la frecuencia a medir, pero falla más en frecuencias muy bajas, donde la
solución vista en este ejercicio proporciona mejores resultados.

SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.10


EJERCICIO 3.5.3: Frecuencímetro digital combinando interrupciones EXT y TIMER0.
Modificamos el programa anterior para que ahora el refresco periódico y visualización
en los dígitos de 7 segmentos se realice mediante interrupciones del TIMER0.
Mantenemos en el programa principal sólo el cálculo periódico (por ejemplo cada
200ms) del valor de la frecuencia de la señal de entrada (la variable count en este caso).

void main() {

//Configuramos temporizadores e interrupciones:


setup_timer_0(T0_INTERNAL | T0_DIV_8);//va a cambiar de dígito cada 8,2 ms aprox.
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8); //prescaler máximo de este TIMER, para llegar a
//medir frecuencias muy bajas
enable_interrupts(INT_TIMER0);
ext_int_edge(L_TO_H);//por defecto ya es así
enable_interrupts(INT_EXT);
enable_interrupts(GLOBAL);

while (TRUE) {
delay_ms(200); //actualizamos la medida cada 200ms
count=100000000/((int32)tiempo * 32); //frecuencia en Hz*100
} //fin del while
}

• Utilizando como referencia el programa del ejercicio 3.5.2, redacta una rutina de
interrupción del TIMER0 para que sea ésta la que ahora realice la visualización en el
display de 4 dígitos; dejando en el main() sólo la actualización periódica de la medida
indicada en el código anterior.
• Al ejecutar el programa con estos cambios observarás que hay más variabilidad de las
medidas que en el ejercicio 3.5.2. ¿Cuál puede ser el motivo?

SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.11


ANEXO:

Directivas y funciones para programación de interrupciones en CCS:

Definición de la rutina de interrupción: igual que una función, encabezada por la


directiva:

#INT_xxxx
En donde xxxx debe corresponderse con una de las posibles interrupciones disponibles
en el microcontrolador que estemos utilizando (la lista completa está en el archivo de
cabecera del micro y también visible en el apartado ‘view>valid interrupts’ del
compilador de CCS).
Para el 16F84, xxxx=TIMER0 o RTCC, EXT o EXT0, EEPROM y RB.

Funciones relacionadas con la configuración y gestión de interrupciones:

ENABLE_INTERRUPTS(xxxx); activa el bit de permiso de la interrupción xxxx, y con


xxxx=GLOBAL activa el bit de permiso global GIE.

DISABLE_INTERRUPTS(xxxx); pone a 0 el bit de permiso de la interrupción xxxx, y


con xxxx=GLOBAL anula el bit de permiso global GIE.

CLEAR_INTERRUPT(xxxx); pone a 0 el flag o bit de petición de interrupción xxxx,

EXT_INT_EDGE(n, flanco); Para una interrupción externa INT_EXTn, indica el


sentido del flanco que producirá la petición de interrupción: flanco = L_TO_H si es
flanco ascendente, y flanco = H_TO_L si es descendente.

SED. Práctica 3. Programación de PIC. Temporizadores e interrupciones. Pág.12

También podría gustarte