Timers Atmega328p

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 12

TIMER 2 ATMEGA328P

Índice

1. Timers 2

2. Lenguaje Arduino - Introducción 2

3. Interrupción 3

4. Timers atmega328p 4

5. Ejemplo de clase resuelto 6

6. Cálculo de frecuencia de ISR 8

1
1. Timers

Un timer es un periférico común dentro de muchos microcontroladores


que existen en el mercado. Su propósito principal es generar una base de
tiempo, para que el microcontrolador realice una acción determinada en un
determinado rango de tiempo controlado por el programador.
La conguración de este periférico, depende del fabricante del microcon-
trolador. En el caso de por ejemplo RP2040 (32 bits), el fabricante provee
una API en C mediante la cual congura el registro propiamente dicho, sin
demasiado esfuerzo por parte del programador. EN otros casos, el fabrican-
te no provee funciones nativas para realizar este manejo de los tiempos y
la conguración del mismo puede realizarse a través de registros del micro-
controlador. En cualquiera de los dos casos, el objetivo del timer es que el
programador realice una acción determinada cada una determinada cantidad
de tiempo. Cabe destacar que el control de estos tiempos es el principio de
operación de un sistema operativo. Sin entrar en detalles, el sistema operativo
toma control de este tiempo, y ejecuta las tareas en orden preasignado. Un
ejemplo de esto puede verse en el sistema operativo Simba o FreeRtos, que
son sistemas operativos destinados a sistemas embebidos y microcontrolado-
res. La discusión acerca de sistemas operativos será temas cercanos a nal de
año del presente ciclo lectivo, según alcance el tiempo y avance del proyecto
nal.
En el presente documento se expone una introducción al timer del micro-
controlador ATmega328p. Para aquel que tenga interés en conocer la arqui-
tectura del timer, puede remitirse a la hoja de datos del mismo.

2. Lenguaje Arduino - Introducción

El IDE arduino, provee un entorno de programación con dos funciones


principales:
void setup(): EL setup se ejecuta por única vez y su objetivo es el de
realizar la inicialización de dispositivos.
void loop(): Este bucle se ejecuta todo el tiempo y es aquél en donde
se debe escribir la lógica del programa o software que se desee imple-
mentar.
A continuación se listan las instrucciones principales que provee arduino
que se utilizan en el repositorio de ejemplo:

2
pinMode(pin,mode): pin es el número de puerto, y mode es la congu-
ración del puerto (INPUT, OUTPUT o INPUT_PULLUP). Dene si
el puerto o pin se congura como entrada o salida
digitalWrite(pin,estado): Si el pin esta congurado como salida, enton-
ces estado es el valor que adquiere ese puerto a la salida.

3. Interrupción

Tal como su nombre lo indica, una interrupción, interrumpe el ujo nor-


mal del programa, realiza una determinada acción, y vuelve al punto desde
donde abandonó el ujo. Cabe destacar que esta acción no modica variables
del código (salvo que se modiquen en la interrupción). Para ejemplicar lo
anterior, si observamos la gura 1. El código esta corriendo una y otra vez so-
bre la función void loop, y en la instrucción 3 (ESTO ES UN EJEMPLO,NO
SIEMPRE SE EJECUTA SOBRE LA INSTRUCCIÓN 3), ocurre un evento
en el sistema, entonces el programa abandona su ujo principal (circulo en
amarillo marcado con 1), luego ejecuta una rutina de interrupción, termina
esa rutina, y vuelve a la instrucción 3 (circulo en amarillo marcado con 2) y
prosigue con su rutina como venia antes.

Figura 1: Ejemplo de interrupción


La forma de congurar una interrupción dentro de cualquier microcon-
trolador es la siguiente:

3
1. Deshabilitar todas las interrupciones.
2. Conguración del periférico y la interrupción.
3. Volver a habilitar todas las interrupciones.

Cabe destacar que estos tres puntos pueden utilizarse en cualquier parte
del código, ya que depende de la aplicación, y en que casos se utiliza. Por
ejemplo, si se sabe que el microcontrolador siempre lee el puerto 4, entonces
conviene congurarse la ISR en el inicio del programa. Si por ejemplo el
puerto 4 se lee una sola vez y la lecutura se hara una sola vez, entonces
puede realizarse la una sola vez, y luego deshabilitarla.
Las interrupciones en general estan asociados a periféricos del microcon-
trolador, y en general son de dos tipos: hardware y software. Las de software
se asocian a eventos como timers, pwm, watchdog, etc. Las de hardware se
asocian en general a buses de comunicación como UART, I2C,SPI, etc y
se comunican con periféricos. Otra ISR común es la de detectar ancos de
subida o bajada en los puertos del microcontrolador.
En el caso del microcontrolador utilizado, microchip (fabricante) provee
una macro denominada ISR, y para ejecutar la rutina de interrupción debe
escribirse el nombre del periférico asociado a la interrupción. La forma de
realizarlo es:
ISR(NOMBREINTERRUPCIÓN{
//AQUI VA LA RUTINA DE INTERRUPCION
}

4. Timers atmega328p

El microcontrolador atmega328p tiene tres timers, denominados TIMER0,


TIMER1, TIMER2. Se verá en el presente trabajo el TIMER2, siendo eso
extensible a los otros timers, que tienen un funcionamiento similar. Las unica
diferencia entre ellos es la cantidad de bits y otros detalles que no se men-
cionarán en el presente texto. Cada uno de estos timers tiene 4 modos de
funcionamiento:
CTC: Utiliza un comparador entre la cuenta y un registro, esto permite
un mejor control del tiempo.
NORMAL: En este modo posee un contador que cuenta hasta un nú-
mero máximo, y luego vuelve a iniciar desde 0.

4
FAST PWM MODE: Modo de PWM, genera una salida pwm con fre-
cuencia y ancho de pulso prejado.
Phase Correct PWM: idéntico al anterior, pero la frecuencia se realiza
al doble del modo anterior.
El modo normal será tratado en este texto. Los modos PWM son utili-
zados para generar una señal PWM en el microcontrolador, donde se puede
congurar la frecuencia del PWM y el ancho de pulso. El modo CTC se verá
en proximas clases.
EL modo normal tiene el siguiente funcionamiento:
1. Ingreso de la frecuencia de clk del sistema (punto 1 en 2).
2. Esta frecuencia se divide por 2(punto 2 en gura 2).
3. Luego, esta se pasa por un preescaler, que elije la frecuencia el progra-
mador (registro TCCR2B, punto 3 en gura 2).
4. Una vez elejida esta frecuencia, empieza a contar un contador de 8
bits. Una vez que este contador desborda (llego a 255) genera una
interrupción (registro TCCR2B, punto 4 en gura 2).
5. Este contador queda siempre contando a la frecuencia seleccionada y
genera una interrupción cada determinada cantidad de tiempo.

Figura 2: Esquema de funcionamiento del timer 2 del ATmega328P en modo


normal
Para congurar la interrupción, primero se deben deshabilitar las inte-
rrupciones , luego congurar el periférico, y despues volver a habilitar las
interrupciones. El proceso para realizar en el microcontrolador atmega328p
es el siguiente:

5
void setup(){
...
SREG = (SREG & 0b01111111); ///deshabilitar interrupciones
TCNT2 = 0 ;
TIMSK2 =TIMSK2|0b00000001;
TCCR2B = 0b00000011 ; //FCLK = 16M/32 = 500k -> sistyck = (1/500k)*255
SREG = (SREG & 0b01111111) | 0b10000000; //Habilitar interrupci
...
}
El registro SREG, es un registro global que habilita/deshabilita interrup-
ciones. La operacion SREG = (SREG & 0b01111111) se encarga de deshabi-
litar todas las interrupciones, mientras que SREG = (SREG & 0b01111111)
| 0b10000000 las vuelve a habilitar. Todo el código que esta entre estas dos
sentencias son para generar la conguración del timer2 en modo normal. Las
sentencias que hay en el medio realizan las siguientes acciones:
1. TCNT2 =0: pone el contador a cero.
2. TIMSK2 =TIMSK2|0b00000001:habilita la interrupción por desborda-
miento del contador.
3. TCCR2B = 0b00000011: congura el timer a la frecuencia que desea
el programador. Para conocer que frecuencias tiene disponible el pro-
gramador debe remitirse a la hoja de datos del microcontrolador. Los
valores para congurar el preescaler son los últimos tres bits.
Una vez congurado el timer2, en modo normal, se realiza la rutina de
interrupción, que tiene el siguiente formato de código
ISR(TIMER2_OVF_vect)
{
sistyck_timer_2++ ;
}
En este caso, lo único que realiza es el incremento de una variable. Esta
variable se toma desde la función loop principal, y al incrementarse una
determinada cantidad de veces, se sabe con exactitud cada cuanto tiempo se
ejecuta determinada rutina.

5. Ejemplo de clase resuelto

El objetivo del ejercicio es realizar el cambio de la x seg por la cantidad


de segundos pasados. Se sabe que la frecuencia de entrada son 16Mhz

6
volatile uint16_t sistyck_timer_2 = 0 ;
uint8_t state_led = LOW ;
void setup() {
Serial.begin(9600) ;
pinMode(13,OUTPUT) ;
digitalWrite(13,state_led) ;
Serial.print("config timer") ;
//// configuración del timer en modo normal
SREG = (SREG & 0b01111111); ///deshabilitar interrupciones
TCNT2 = 0 ;
TIMSK2 =TIMSK2|0b00000001;
TCCR2B = 0b00000011 ; //FCLK = 16M/32 = 500k -> sistyck = (1/500k)*255
SREG = (SREG & 0b01111111) | 0b10000000; //Habilitar interrupciones

void loop() {
if (sistyck_timer_2 >= 2000){
Serial.println("han pasado x seg") ;
sistyck_timer_2 = 0 ;
state_led = state_led == LOW?HIGH:LOW ;
digitalWrite(13,state_led) ;

ISR(TIMER2_OVF_vect)
{
sistyck_timer_2++ ;
}

El primer paso para calcular la frecuencia de la interrupción es conocer la


frecuencia de entrada, luego dividirla por 2, acto seguido ver que preescaler
esta seleccionado. Con estos datos se obtiene la frecuencia de la interrupción.
La frecuencia de entrada en este caso es 16Mhz. Al dividirla por 2, quedan
8 Mhz. Luego se observa que TCCR2B = 0b00000011. Los bits que conguran
el preescaler son los últimos 3 bits. Es decir el preescaler esta congurado
en 011. Si observamos la tabla de la hoja de datos del microcontrolador
AtMega328p, se tiene que 011 equivale a dividir la frecuencia de entrada por

7
32. Resumiendo los pasos(remitase a la gura 2):

Figura 3: Preescaler del timer 2 atmega328p.

1. Frecuencia de entrada (punto 1 gura2) = 16Mhz


2. Frecuencia de salida primer preescaler (punto 2 gura2) = 8Mhz
3. Frecuencia de salida segundo preescaler (punto 3 gura2) = 8Mhz/32
= 250kHZ.
El contador tiene una frecuencia de 250Khz, o lo que es equivalente en
periodo de T = 1/250K = 4 µs. Entonces el contador cuenta 1 a 4µs, 2 a 8µs
y así siguiendo. Si el contador llega a 255 y genera la interrupción, entonces
la interrupción se genera cada TISR = 255 × 2µs = 1,02ms
Con esta conguración cada interrupción se genera cada 1.02 ms. Si una
interrupción se genera cada 1.02 ms, entonces esto implica que cada vez que
se ejecuta la interrupción, la variable se incrementa en uno, luego en 2, y
asi sucesivamente. Vemos que el código del void loop, tiene que debe ser
mayor o igual a 2000. Realizando una regla de tres simple, puede obtenerse
la cantidad de segundos. Si una ISR ocurre cada 0.51 ms, entonces 2000 isr
ocurren cada 1.02ms * 2000 = 2.04 s
La respuesta es x=2.04 s, y se debe reemplazar por 2.04. Puede simular
el circuito en proteus o similar para ver la forma de onda.

6. Cálculo de frecuencia de ISR

Ahora en esta etapa, vamos a suponer el problema al revés. Queremos


encender y apagar un puerto del microcontrolador cada 10 ms.
Entonces el primer paso, es ver que frecuencia de ISR es mejor. Con base
en la gura 3, y una frecuencia de entrada de 16 Mhz, se debe vericar cual
es el mejor "tiempo"para satisfacer este requerimiento.

8
Se va a utilizar el caso de N=8 (segunda la de 3), pero esto no implica
que sea la mejor. Sino será un ejemplo de cálculo para que vea como es el
desarrollo.
Si se elije N=8, la frecuencia de entrada es 16 Mhz, entonces, la salida
del segundo preescaler es 1 MHz. Para verlo puede remitirse a la gura 2. Si
fclk = 16M hz (punto 1 gura 2), la salida es fclk /2 = 8M hz (punto 2 gura
2), al elejir el preescaler de 8, la salida es de fclk /8 = 1M hz (punto 3 gura
2). Luego la interrupción se va a ejecutar cada Tisr = 1µs × 255 = 0,25ms.
Por lo expuesto, la forma de congurar el timer2 dentro de la funcion setup
es:
void setup(){
SREG = (SREG & 0b01111111); ///deshabilitar interrupciones
TCNT2 = 0 ;
TIMSK2 =TIMSK2|0b00000001;
TCCR2B = 0b00000010 ; // <- ver que los ultimos 3 bits coinciden
SREG = (SREG & 0b01111111) | 0b10000000; //Habilitar interrupcion
}
Ahora, dado que una interrupción de timer se ejecuta cada 0.25ms, de-
bemos saber cuantas interrupciones se deben contar para tener 10 ms. Esto
se realiza realizando una regla de tres simple: si una interrupción se ejecuta
cada 0.25 ms, entonces en 10 ms se tiene que: 10ms/0.25ms = 40 isr. Por lo
tanto, dentro de la interrupción hay una variable que se incrementa y cuan-
do llega a 40, se debe ejecutar la acción. El código dentro de la función loop
queda:
uint16_t timer_inc = 0 ; //VARIABLE 16 BITS SIN SIGNO
void loop(){
if (timer_inc >=40){
// codigo que se ejecuta cada 10 ms
timer_inc = 0 ; // <- EVITA EL DESBORDE DE LA VARIABLE
}
}

ISR(TIMER2_OVF_vect)
{
timer_inc++ ; // EQUIVALE A timer_inc = timer_inc + 1 ;
}
Si por ejemplo quisieramos mas de una función para ejecutarse, por ejem-
plo una por segundo, y otra a los tres segundos, se debe repetir un proceso

9
similar. Siguiendo con el caso, si se quiere una función cada segundo, se debe
realizar 1000ms/0.25ms = 4000, y la otra a los tres segundos 3000ms/0.25ms
= 12000. Entonces se deben agregar dos variables mas, una que llegue a 4000,
y otra a 12000. El código queda como sigue dentro de la función loop.
uint16_t timer_inc = 0 ; //VARIABLE 16 BITS SIN SIGNO para tarea de 10 ms
uint16_t timer_inc1 = 0 ; //VARIABLE 16 BITS SIN SIGNO para tarea de 1 se
uint16_t timer_inc2 = 0 ; //VARIABLE 16 BITS SIN SIGNO para tarea de 3 se

void loop(){
if (timer_inc >=40){
// codigo que se ejecuta cada 10 ms
timer_inc = 0 ; // <- EVITA EL DESBORDE DE LA VARIABLE
}
if (timer_inc1 >=4000){
// codigo que se ejecuta cada 1 segundo
timer_inc1 = 0 ; // <- EVITA EL DESBORDE DE LA VARIABLE
}
if (timer_inc >=12000){
// codigo que se ejecuta cada 3 segundos
timer_inc2 = 0 ; // <- EVITA EL DESBORDE DE LA VARIABLE
}
}

ISR(TIMER2_OVF_vect)
{
timer_inc++ ; // EQUIVALE A timer_inc = timer_inc + 1 ;
timer_inc1++ ;
timer_inc2++ ;
}
El código nalmente ordenado queda:
void setup(){
....
SREG = (SREG & 0b01111111); ///deshabilitar interrupciones
TCNT2 = 0 ;
TIMSK2 =TIMSK2|0b00000001;
TCCR2B = 0b00000010 ; // <- ver que los ultimos 3 bits se sacan de la tabla
SREG = (SREG & 0b01111111) | 0b10000000; //Habilitar interrupciones
...

10
}

uint16_t timer_inc = 0 ; //VARIABLE 16 BITS SIN SIGNO para tarea de 10 ms


uint16_t timer_inc1 = 0 ; //VARIABLE 16 BITS SIN SIGNO para tarea de 1 seg
uint16_t timer_inc2 = 0 ; //VARIABLE 16 BITS SIN SIGNO para tarea de 3 seg

void loop(){
if (timer_inc >=40){
// codigo que se ejecuta cada 10 ms
timer_inc = 0 ; // <- EVITA EL DESBORDE DE LA VARIABLE
}
if (timer_inc1 >=4000){
// codigo que se ejecuta cada 1 segundo
timer_inc1 = 0 ; // <- EVITA EL DESBORDE DE LA VARIABLE
}
if (timer_inc >=12000){
// codigo que se ejecuta cada 3 segundos
timer_inc2 = 0 ; // <- EVITA EL DESBORDE DE LA VARIABLE
}
}

ISR(TIMER2_OVF_vect)
{
timer_inc++ ; // EQUIVALE A timer_inc = timer_inc + 1 ;
timer_inc1++ ;
timer_inc2++ ;
}

11

También podría gustarte