Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Práctica 3.
Título: Programación de microcontrolador PIC. Temporizadores e
interrupciones.
Desarrollo:
I) Uso del temporizador TIMER0 del PIC16F84 en modo contador de pulsos externos.
IV) Control del multiplexado en el tiempo del display mediante interrupciones del
temporizador TIMER0.
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).
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
….
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
}
}
…
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.
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):
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.
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;
}
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.
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.
#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
}
void main() {
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?
#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.