← Índice del curso

Microcontroladores · Tema 23

Temporizadores y contadores

El tiempo en un microcontrolador no se mira, se cuenta. Un temporizador es un contador binario que avanza con el reloj, y casi todo lo que sigue en este curso (marcas de tiempo, retardos, PWM, medición de pulsos, comunicación) se apoya en él. Ya usaste tres temporizadores como cajas negras; ahora los abrimos.

01 · Punto de partida

Contar para medir el tiempo

En el curso de circuitos secuenciales construiste contadores con flip-flops: cada flanco del reloj incrementaba un número binario, y un contador de n bits desbordaba cada 2n pulsos. Un temporizador de microcontrolador es exactamente eso, con tres añadidos que lo vuelven una herramienta general: un divisor de frecuencia configurable (el prescaler), uno o más comparadores y un conjunto de modos que deciden qué pasa al llegar a ciertos valores.

Ya los usaste: el temporizador 0 como base de 1 ms (tema 11), el 1 como compuerta de 1 s y el 2 como generador de señal (tema 22). Faltaba una mirada de conjunto, que es el objeto de este tema y de los siguientes: cómo se organizan, qué resolución y qué rango dan, y cuándo conviene cada modo.

Retomamos los contadores y la división de frecuencia del curso de circuitos secuenciales, el tema 11 (modo CTC del temporizador 0) y el reloj del tema 12.

02 · Las piezas

Anatomía de un temporizador

Bloques de un temporizador El reloj de la CPU o un pin externo entra a un prescaler. La salida alimenta el registro contador TCNT, controlado por la lógica del modo. Un comparador contrasta TCNT con el registro OCR y, junto con el desbordamiento, activa las banderas y las interrupciones. Relojde la CPU Prescaler÷1 … ÷1024 ContadorTCNTn (8 o 16 bits) ComparadorTCNT = OCR? BanderasTOVn, OCFn → interrupción Pin Tnreloj externo Modo (WGM)TOP y borrado Registro OCRvalor de comparación

Cada bloque tiene su registro:

  • Contador (TCNTn): el valor actual de la cuenta, de lectura y escritura.
  • Prescaler y fuente de reloj (CSn2:0 en TCCRnB): divide el reloj de la CPU, o toma el pin externo. Con CS = 000 el temporizador está detenido.
  • Modo (WGMn en TCCRnA/TCCRnB): qué hace el contador al llegar al valor máximo (TOP) y cómo se genera la salida.
  • Comparadores (OCRnA, OCRnB): valores contra los que se compara la cuenta; al coincidir se marca una bandera o se invierte un pin.
  • Banderas (TIFRn: TOVn, OCFnA, OCFnB) y su máscara de interrupción (TIMSKn).

Es un circuito secuencial síncrono: en cada pulso, el contador suma uno y el comparador compara. Todo lo demás se arma combinando esas dos operaciones.

03 · Qué hay en el chip

Los tres temporizadores

Temporizadores del ATmega328P.
TemporizadorBitsCanales de comparaciónPrescalersParticularidades
Timer08A y B (pines PD6 y PD5)÷1, 8, 64, 256, 1024 y reloj externo en T0 (PD4)Comparte el prescaler con el temporizador 1
Timer116A y B (pines PB1 y PB2) y captura de entrada (ICP1, PB0)÷1, 8, 64, 256, 1024 y reloj externo en T1 (PD5)Rango mucho mayor; modos con ICR1 como TOP
Timer28A y B (pines PB3 y PD3)÷1, 8, 32, 64, 128, 256, 1024Prescaler propio; puede funcionar con un cristal de 32,768 kHz en modo asíncrono

La diferencia de bits importa: un contador de 8 bits cuenta hasta 256 y uno de 16 bits hasta 65 536. A igual prescaler, el temporizador 1 cubre 256 veces más tiempo, o la misma duración con una resolución 256 veces mejor.

Dos advertencias para elegir:

  • Otros usos del mismo hardware. Las salidas PWM, la comunicación y la biblioteca de Arduino reservan temporizadores. En el núcleo de Arduino, millis(), micros() y delay() dependen del temporizador 0; la biblioteca Servo, del 1; y tone(), del 2. Si mezclás código en C con funciones de Arduino, reconfigurar uno de esos temporizadores rompe las funciones que lo usan. Con C puro (el enfoque del curso) no hay ese problema.
  • Energía. Un temporizador sin usar puede apagarse del todo con los bits PRTIM0, PRTIM1 y PRTIM2 del registro PRR (power reduction register). Lo retomaremos en el bloque de energía.

04 · Cuánto vale cada cuenta

Prescaler y resolución

El prescaler divide el reloj antes de que llegue al contador. Con un reloj de f y un prescaler N, cada cuenta dura N / f: esa es la resolución del temporizador, la unidad mínima de tiempo que distingue. Y el contador desborda cada N · 2bits / f: ese es su alcance.

Resolución y desbordamiento a 16 MHz.
PrescalerCada cuenta duraDesborda (8 bits)Desborda (16 bits)
÷162,5 ns16 µs4,096 ms
÷80,5 µs128 µs32,768 ms
÷644 µs1,024 ms262,1 ms
÷25616 µs4,096 ms1,049 s
÷102464 µs16,384 ms4,194 s

Es el mismo compromiso del tema 14: una resolución fina implica un alcance corto, y viceversa. Por ejemplo, para medir 50 ms con una resolución de 1 µs harían falta 50 000 cuentas, que cabrían en 16 bits, pero el chip no tiene un prescaler ÷16: con ÷8 (0,5 µs) son 100 000 cuentas y no entran, y con ÷64 (4 µs) entran pero con peor resolución. La solución es aceptar la resolución que ofrece el prescaler disponible, o ampliar el contador por software contando desbordamientos (sección 5).

Los dos temporizadores 0 y 1 comparten un solo prescaler, de modo que al reiniciarlo para sincronizar uno se afecta al otro. Es un detalle que solo importa cuando se necesita una sincronización exacta entre ambos.

05 · El modo más simple

Modo normal y desbordamiento

En el modo normal el contador sube de 0 hasta el máximo (255 o 65 535) y vuelve a 0, sin detenerse. Al pasar de máximo a cero se marca la bandera de desbordamiento TOVn. Las comparaciones con OCRn también marcan su bandera cuando la cuenta coincide, pero no alteran el conteo.

Sirve de dos maneras:

  • Como reloj de libre marcha (free-running): el contador cuenta sin parar y se lo lee cuando se necesita una marca de tiempo. Es el modo ideal para medir intervalos (sección 8).
  • Como base de tiempo por desbordamiento: la interrupción de desbordamiento incrementa un contador de software. Es el principio de millis().
volatile uint32_t desbordamientos = 0;

ISR(TIMER0_OVF_vect) {                 // cada 1,024 ms con prescaler 64
    desbordamientos++;
}

TCCR0A = 0;                            // modo normal
TCCR0B = (1 << CS01) | (1 << CS00);  // ÷64
TIMSK0 |= (1 << TOIE0);
sei();

El inconveniente: con 16 MHz y ÷64, el período es de 1,024 ms, no de 1 ms. Si cada desbordamiento se contara como «1 ms», el reloj atrasaría un 2,3 %. La solución habitual es sumar el período real (1024 µs) y llevar la fracción, que es lo que hace Arduino. La alternativa más limpia es usar el modo CTC y elegir un valor de comparación que dé exactamente 1 ms.

Para ampliar el alcance con un temporizador de 8 bits, el tiempo total es desbordamientos × 256 + TCNT0 (en cuentas). Hay que leerlos de forma consistente: la ISR puede incrementar los desbordamientos entre las dos lecturas, un caso de lectura partida del tema 21.

06 · Elegir el período

Modo CTC

En CTC (clear timer on compare match) el contador no sube hasta el máximo, sino hasta el valor de OCRnA, y vuelve a cero. El período queda a tu elección:

período = (OCRnA + 1) · N / f

La bandera OCFnA se marca en cada coincidencia y puede generar una interrupción, o invertir el pin OCnA por hardware (tema 15). Se activa con WGMn1 (en los temporizadores de 8 bits) o WGM12 (en el de 16). En el temporizador 1 también existe un CTC con ICR1 como TOP, que libera OCR1A para otro uso.

Para elegir el prescaler hay una receta de tres pasos, la que usaste en los talleres: (1) calcular las cuentas necesarias, período · f / N, para cada prescaler; (2) descartar los que no entran en el contador (más de 256 o 65 536); (3) quedarse con el que dé un número entero exacto, o el de menor error, y, entre iguales, el de menor prescaler (mejor resolución). La calculadora del laboratorio hace esos pasos para los tres temporizadores.

07 · El reloj de afuera

Contar eventos externos

Un temporizador no tiene por qué contar el reloj de la CPU. Los bits CSn2:0 permiten tomar como reloj un pin: T0 (PD4) para el temporizador 0 y T1 (PD5) para el 1.

Fuentes de reloj externas (CSn2:0 = 110 y 111, para los temporizadores 0 y 1).
CSn2:0Cuenta…
110Un pulso por cada flanco descendente del pin
111Un pulso por cada flanco ascendente del pin

Así el temporizador pasa a ser un contador de eventos por hardware: cuenta pulsos sin consumir un solo ciclo de la CPU. El máximo es la mitad de la frecuencia del reloj (8 MHz a 16 MHz), muy por encima de lo que logra una interrupción (unos 363 kHz, tema 22). Para medir una frecuencia, se deja contar el temporizador 1 desde el pin T1 durante una compuerta de tiempo dada por otro temporizador, y se lee TCNT1 al final.

El temporizador 2 no tiene entrada de reloj externa por pin, pero sí un modo asíncrono: puede funcionar con un cristal de 32,768 kHz en los pines TOSC1 y TOSC2 y seguir contando mientras el resto del chip duerme. Esa es la base de un reloj de tiempo real de bajo consumo.

08 · Medir intervalos

Leer la cuenta y medir intervalos

Leer TCNTn en un temporizador de 16 bits requiere dos accesos y un registro temporal compartido (tema 9). Con el nombre completo en C, el compilador respeta el orden; si una interrupción usa el mismo registro temporal, la lectura debe protegerse (tema 21).

Para medir cuánto tardó algo, se lee la cuenta antes y después y se resta, usando aritmética sin signo:

TCCR1A = 0;
TCCR1B = (1 << CS11);                 // modo normal, ÷8: una cuenta cada 0,5 µs

uint16_t inicio = TCNT1;
/* … código que se quiere medir … */
uint16_t cuentas = TCNT1 - inicio;     // la resta módulo 65 536 absorbe la vuelta

// tiempo en microsegundos = cuentas / 2

La resta de enteros sin signo de 16 bits equivale a una resta módulo 65 536: aunque el contador haya dado la vuelta entre las dos lecturas (por ejemplo, de 65 000 a 1 000), el resultado es correcto (1536 cuentas). Es la técnica que usan las bibliotecas de temporización de todos los microcontroladores.

Su límite: el intervalo medido no puede superar una vuelta completa del contador (32,768 ms en este ejemplo). Si el código tardó una vuelta y media, la medición da la mitad de una vuelta, sin aviso. Para intervalos más largos hay que contar los desbordamientos o bajar la resolución con un prescaler mayor.

El mismo patrón sirve para timeouts sin bloquear: guardar una marca de tiempo y, en cada vuelta del bucle principal, comprobar si (ahora − marca) ≥ plazo. Es el sustituto de _delay_ms() para programas que hacen varias cosas a la vez.

09 · Analogía con programación

La deriva de contar mal y la resta módulo 2n

Este programa calcula, para el temporizador 0 con prescaler 64, el desbordamiento de 1,024 ms; compara tres formas de contar milisegundos durante 10 s reales (un desbordamiento = 1 ms, sumar 1024 µs por desbordamiento, y el modo CTC exacto); y muestra cómo la aritmética modular absorbe la vuelta de un contador de 16 bits.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

F_CPU = 16_000_000


def desborda_cada(preescaler, bits):
    """Tiempo entre desbordamientos del contador (modo normal)."""
    return preescaler * (1 << bits) / F_CPU


# 1) El temporizador 0 con prescaler 64 desborda cada 1,024 ms, no cada 1 ms.
periodo = desborda_cada(64, 8)
print(f"Timer0, ÷64: desborda cada {periodo * 1000:.3f} ms ({1 / periodo:.2f} Hz)")

# 2) Contar milisegundos de dos maneras durante 10 segundos reales.
real_s = 10
desbordamientos = int(real_s / periodo)
ingenuo_ms = desbordamientos * 1                      # «1 desbordamiento = 1 ms»: incorrecto
acumulado_us = desbordamientos * 1024                  # sumar el período real en microsegundos
print(f"   {desbordamientos} desbordamientos en {real_s} s reales")
print(f"   contando 1 ms por desbordamiento: {ingenuo_ms} ms   (error {100 * (ingenuo_ms / 1000 - real_s) / real_s:+.2f} %)")
print(f"   sumando 1024 µs por desbordamiento: {acumulado_us // 1000} ms")
print(f"   modo CTC con OCR0A = 249 (exacto): cada marca es 1 ms -> {int(real_s * 1000)} marcas en {real_s} s")

# 3) Tiempo transcurrido con un contador de 16 bits que da la vuelta.
def transcurrido(ahora, inicio, bits=16):
    return (ahora - inicio) % (1 << bits)             # la aritmética modular absorbe la vuelta


print()
for inicio, ahora in ((1_000, 5_000), (65_000, 1_000), (60_000, 60_000)):
    ticks = transcurrido(ahora, inicio)
    print(f"inicio {inicio:>6}, ahora {ahora:>6} -> {ticks:>5} cuentas = {ticks * 0.5:.1f} µs  (÷8 a 16 MHz)")
print(f"alcance máximo sin ambigüedad: {(1 << 16) * 0.5 / 1000:.3f} ms")

Resultado: en 10 s ocurren 9765 desbordamientos; contarlos como milisegundos da 9765 ms (−2,35 %). Sumar 1024 µs por desbordamiento da 9999 ms (error de menos de 1 ms), y el modo CTC con OCR0A = 249 da 10 000 marcas exactas. En la tercera parte, entre 65 000 y 1000 hay 1536 cuentas (768 µs) a pesar de la vuelta. Probá cambiar real_s a 60 o el prescaler a 256.

El modelo es ideal: no incluye la latencia de la interrupción ni el tiempo con las interrupciones deshabilitadas, que pueden hacer perder un desbordamiento si dura más que el período.

10 · Exploración interactiva

Calculadora y simulador

La calculadora evalúa todos los prescalers de cualquiera de los tres temporizadores para un período dado: resolución, desbordamiento y el valor de OCRnA en modo CTC, y genera el código. El simulador muestra el contador paso a paso, en modo normal y en CTC, con sus banderas.

Banco de temporizadores

1. Calcular la configuración

Cada prescaler del temporizador elegido. La fila resaltada es la mejor opción en modo CTC.
PrescalerCada cuenta duraDesborda cada…OCRnA para el períodoError

2. Simulador de la cuenta (8 bits)

TCNT00 (00000000)
TOV00
OCF0A0
Cuentas transcurridas0
Eventos—
Tiempo transcurrido—

    El simulador reproduce el recuento a nivel de las cuentas; no modela la latencia de la bandera (el hardware real la marca un ciclo del temporizador después de la coincidencia) ni las salidas por pin. El tiempo transcurrido es el producto de las cuentas por la duración de cada una.

    Recorrido guiado

    1. En la calculadora, Timer0 con 1 ms a 16 MHz: ÷64 con OCR0A = 249 exacto, y desborda cada 1,024 ms. Es la receta del tema 11.
    2. Cambiá a Timer1 con 1 s: ÷256 y OCR1A = 62499, o ÷1024 y 15 624. Con Timer0 y 100 ms no hay opción: el mensaje sugiere el temporizador 1.
    3. Probá Timer2 con 2 ms: ÷128 con OCR2A = 249 es exacto (el temporizador 2 tiene prescalers que no tienen los otros).
    4. En el simulador, en modo normal, avanzá +100 tres veces: pasan 300 cuentas y el contador, que dio la vuelta una vez, vale 44; TOV0 quedó en 1.
    5. Pasá a CTC con OCR0A = 9: la cuenta recorre de 0 a 9 y vuelve a 0 cada diez cuentas. En 100 cuentas hay 10 coincidencias. Borrá las banderas y repetí.
    Desafío: el período más largo con 8 bits

    Con un reloj de 16 MHz, ¿cuál es el período más largo que se logra con un temporizador de 8 bits en CTC? ÷1024 y OCR0A = 255: 256 × 1024 / 16 000 000 = 16,384 ms. Probalo en la calculadora pidiendo 17 ms con el Timer0: ninguna opción alcanza. Con el Timer1 sí.

    11 · Método de trabajo

    Cómo elegir y configurar

    1. Definí qué necesitás: una marca periódica, un retardo, un timestamp, contar eventos, medir un pulso.
    2. Calculá el rango y la resolución que pide el problema: el intervalo más largo y la unidad más pequeña.
    3. Elegí el temporizador: de 16 bits para rangos grandes o resolución fina; de 8 bits para marcas simples. Verificá que no lo estén usando otros periféricos o bibliotecas.
    4. Elegí el modo: normal para un reloj de libre marcha o timestamps; CTC para un período a medida.
    5. Calculá el prescaler y el OCR con la receta (cuentas enteras exactas, menor prescaler posible).
    6. Arrancá el temporizador al final (escribir CSn lo pone en marcha), después de configurar modo, comparación e interrupciones.
    7. Verificá con una marca en un pin y el osciloscopio: el período medido debe coincidir con el calculado.

    Si el período sale distinto de lo esperado, el orden de sospecha es: el «+1» del OCR, el prescaler (¿es el del temporizador 2, que tiene otra tabla?), el reloj real de la CPU (tema 12), y por último si una interrupción más lenta que el período está perdiendo marcas.

    12 · Comprensión

    Confusiones frecuentes

    «Todos los temporizadores tienen los mismos prescalers»

    Los temporizadores 0 y 1 comparten los valores ÷1, 8, 64, 256 y 1024, pero el 2 tiene ÷1, 8, 32, 64, 128, 256 y 1024, con otra codificación de los bits CS. Copiar la configuración de uno a otro sin mirar la tabla es una fuente de errores.

    «El temporizador de 16 bits siempre es mejor»

    Tiene más rango y precisión, pero hay solo uno, y suele estar reservado para tareas exigentes (captura de entrada, PWM de 16 bits). Para una marca de 1 ms basta con uno de 8 bits.

    «Un desbordamiento por milisegundo con ÷64 y 8 bits»

    Con 16 MHz es cada 1,024 ms, no 1 ms. Hay que acumular el período real o usar CTC para obtener exactamente 1 ms.

    «Puedo medir cualquier intervalo restando dos lecturas de TCNT»

    Solo si es menor que una vuelta completa del contador. Si no, el resultado está truncado módulo el rango, sin ningún aviso.

    «Detener el temporizador es poner TCNT en 0»

    Se detiene con CSn2:0 = 000. Escribir un 0 en TCNT solo reinicia la cuenta y el temporizador sigue corriendo.

    13 · Práctica guiada

    Predecí antes de comprobar

    1. Alcance del temporizador 1

    Con 16 MHz y prescaler ÷8, ¿cuánto dura cada cuenta y cuánto tarda el temporizador 1 en desbordar?

    Ver solución

    Cada cuenta: 8 / 16 MHz = 0,5 µs. Desborda tras 65 536 × 0,5 µs = 32,768 ms.

    2. Un período en CTC

    Querés 10 ms con el temporizador 0 y prescaler ÷1024 a 16 MHz. ¿Qué OCR0A usás y qué error tiene?

    Ver solución

    10 ms × 16 MHz / 1024 = 156,25 cuentas → 156 cuentas, OCR0A = 155. Período real: 156 × 64 µs = 9,984 ms, un error de −0,16 %.

    3. Leer el período de una configuración

    Con 16 MHz, CTC, prescaler ÷8 y OCR1A = 199, ¿cuál es el período?

    Ver solución

    (199 + 1) × 8 / 16 000 000 = 100 µs (10 kHz).

    4. Medir 50 ms

    Querés medir un evento de unos 50 ms con el temporizador 1 a 16 MHz. ¿Qué prescaler usás y qué resolución obtenés?

    Ver solución

    Con ÷8 el alcance es 32,8 ms (no alcanza). Con ÷64 es 262 ms y la resolución, 4 µs: es el menor prescaler que sirve.

    5. Contar pulsos por hardware

    ¿Qué valor de TCCR1B hace que el temporizador 1 cuente los flancos ascendentes de T1 (PD5)?

    Ver solución

    CS12:0 = 111: TCCR1B = (1 << CS12) | (1 << CS11) | (1 << CS10);. El pin T1 debe ser una entrada. La frecuencia máxima es la mitad del reloj.

    14 · Síntesis

    Ideas para recordar

    • Un temporizador es un contador (TCNTn) alimentado por el reloj dividido por un prescaler, con comparadores (OCRn), modos y banderas.
    • El ATmega328P tiene el Timer0 (8 bits), el Timer1 (16 bits) y el Timer2 (8 bits, con otros prescalers y modo asíncrono).
    • Cada cuenta dura N/f; el contador desborda cada N · 2bits/f. Resolución fina y alcance largo se compensan.
    • Modo normal: sube hasta el máximo y desborda (TOVn); sirve para timestamps y bases de tiempo por desbordamiento.
    • Modo CTC: sube hasta OCRnA y vuelve a 0; período = (OCRnA + 1) · N / f.
    • Con un pin como reloj, el temporizador cuenta eventos por hardware hasta la mitad del reloj de la CPU.
    • Los intervalos se miden restando lecturas con aritmética sin signo, siempre que sean menores que una vuelta del contador.

    ¿Listo para el tema 24?

    Antes de avanzar, calculá la resolución y el desbordamiento de un temporizador para un prescaler dado, elegí prescaler y OCR para un período dado y explicá por qué la resta de dos lecturas funciona aunque el contador desborde. Si podés justificar las tres respuestas, dominás el núcleo del bloque.

    En el próximo tema vemos comparación, captura y base de tiempos: la interrupción periódica, la medición precisa de pulsos con la captura de entrada y una base de tiempo completa.