← Índice del curso

Microcontroladores · Tema 24

Comparación, captura y base de tiempos

El contador cuenta; la unidad de comparación avisa cuando llega a un valor, y la unidad de captura anota qué valor tenía cuando ocurrió algo afuera. Con estas dos piezas, el hardware genera señales y mide pulsos con una exactitud que ninguna rutina de interrupción puede igualar.

01 · Punto de partida

Dos unidades, una dirección cada una

En el tema 23 viste el contador y sus dos modos básicos. Alrededor del contador hay dos unidades de hardware que apuntan en direcciones opuestas:

Comparación (hacia afuera)

Compara la cuenta con un valor que vos elegís (OCRn). Al coincidir, marca una bandera, dispara una interrupción y, si querés, cambia un pin por hardware. Sirve para producir eventos en el instante exacto.

Captura (hacia adentro)

Cuando ocurre un flanco en un pin, copia la cuenta en un registro (ICR1). Sirve para medir cuándo pasó algo, con la exactitud del contador y sin depender de la latencia de la CPU.

La distinción es la misma que entre una salida y una entrada, solo que en el dominio del tiempo. La captura existe únicamente en el temporizador 1 del ATmega328P; la comparación, en los tres.

Retomamos los temporizadores del tema 23, la latencia de las interrupciones (tema 19), la lectura de 16 bits (tema 21) y el contador por hardware del tema 22.

02 · Avisar en el instante justo

La unidad de comparación

Cada temporizador tiene uno o dos canales de comparación (A y B), cada uno con su registro OCRnA u OCRnB. El hardware compara permanentemente TCNTn con ese registro; al coincidir:

  • se marca la bandera OCFnA (o OCFnB) en TIFRn;
  • si la interrupción está habilitada (OCIEnA en TIMSKn), se ejecuta la ISR TIMERn_COMPA_vect;
  • según la configuración, el pin de salida OCnA se invierte, se baja o se sube.

En el modo CTC, el canal A además define el valor máximo (tema 23). Pero en el modo normal, los canales A y B son comparadores independientes sobre un contador que sigue corriendo: se puede pedir un evento cuando la cuenta llegue a 5000 y otro cuando llegue a 12 000, de modo que un solo temporizador hace de varios.

La coincidencia es exacta en el sentido del hardware: el flanco del pin cambia en el ciclo del temporizador que sigue a la coincidencia, sin intervención del programa. La interrupción, en cambio, sufre la latencia habitual. La diferencia entre ambas es la clave de lo que sigue.

03 · Salidas por hardware

Acciones sobre el pin

Los bits COMnx1:0 de TCCRnA (compare output mode) deciden qué hace el pin OCnx en cada coincidencia, en los modos que no son PWM:

Acción del pin en una coincidencia (modos normal y CTC).
COMnx1:0AcciónUso típico
00El pin funciona como un GPIO comúnComparación solo por bandera o interrupción
01Invertir el pinOnda cuadrada de frecuencia exacta (como en el tema 22)
10Bajar el pin a 0Terminar un pulso
11Subir el pin a 1Empezar un pulso

Combinando los canales A y B se arman pulsos de ancho y posición exactos sin una sola instrucción de CPU por flanco: por ejemplo, subir el pin cuando la cuenta alcanza A y bajarlo cuando alcanza B (el ancho es B − A cuentas). Además existe la bandera de forzar comparación (FOCnx), que simula la coincidencia por software para fijar el estado inicial del pin.

El pin debe configurarse además como salida en DDRx: el temporizador maneja el nivel, pero no la dirección. En los modos de PWM (tema 25) las acciones de COM tienen otra tabla.

04 · Una técnica clásica

Eventos periódicos sin deriva

Con el modo CTC, el período queda atado a OCRnA y el contador se reinicia en cada coincidencia. Hay otra forma que libera el contador: dejarlo correr en modo normal y, en cada coincidencia, programar la siguiente sumando el período al valor de OCRn:

#define PERIODO_A 20000        // 10 ms con ÷8 (0,5 µs por cuenta)
#define PERIODO_B  6000        //  3 ms

ISR(TIMER1_COMPA_vect) {
    OCR1A += PERIODO_A;        // el próximo evento, respecto del anterior (módulo 65 536)
    /* tarea A, cada 10 ms */
}

ISR(TIMER1_COMPB_vect) {
    OCR1B += PERIODO_B;
    /* tarea B, cada 3 ms */
}

/* Inicialización */
TCCR1A = 0;
TCCR1B = (1 << CS11);         // modo normal, ÷8
OCR1A  = PERIODO_A;
OCR1B  = PERIODO_B;
TIMSK1 = (1 << OCIE1A) | (1 << OCIE1B);
sei();

Hay tres ventajas. Primera, no hay deriva: el próximo evento se calcula con el valor programado, no con la cuenta del momento en que corre la ISR, de modo que la latencia de una interrupción no se acumula. Segunda, varios períodos independientes de un mismo temporizador (A y B, y el desbordamiento como tercero). Tercera, la suma en 16 bits se desborda sola (OCR1A + 20000 módulo 65 536), igual que el contador.

Su límite: cada período no puede superar una vuelta (32,768 ms con ÷8). Para plazos más largos se cuenta un número de eventos en la ISR.

05 · Medir con el hardware

La captura de entrada

El temporizador 1 tiene un pin especial, ICP1 (PB0). Cuando en ese pin ocurre el flanco elegido, el hardware copia el valor de TCNT1 en el registro ICR1 en ese mismo instante, marca la bandera ICF1 y, si está habilitada, dispara TIMER1_CAPT_vect.

Bits de configuración de la captura (TCCR1B) y su interrupción.
BitRegistroFunción
ICES1TCCR1B, bit 6Flanco de captura: 1 = ascendente, 0 = descendente
ICNC1TCCR1B, bit 7Cancelador de ruido: exige cuatro muestras iguales antes de capturar (retrasa 4 ciclos)
ICIE1TIMSK1, bit 5Habilita la interrupción de captura
ICF1TIFR1, bit 5Bandera de captura, se borra escribiendo un 1

La diferencia esencial con una interrupción externa (tema 20) es lo que se guarda. Con INT0, la ISR corre unos 8 a 11 ciclos después del flanco —o mucho más si otra rutina estaba corriendo— y si lee TCNT1 en ese momento, la lectura incluye esa latencia, variable de un flanco a otro. Con la captura, el valor ya está guardado en el instante exacto del flanco. La ISR puede tardar lo que quiera (hasta que llegue el próximo flanco) sin afectar la medición.

06 · Las tres mediciones

Medir períodos, anchos y frecuencias

El período, y de ahí la frecuencia

Se captura siempre el mismo flanco y se resta la captura anterior:

volatile uint16_t periodo = 0, anterior = 0;
volatile uint8_t  nuevo = 0;

ISR(TIMER1_CAPT_vect) {
    uint16_t t = ICR1;                 // la cuenta guardada en el flanco
    periodo  = t - anterior;           // resta sin signo: absorbe la vuelta
    anterior = t;
    nuevo    = 1;
}

TCCR1A = 0;
TCCR1B = (1 << ICES1) | (1 << CS11);   // flanco ascendente, ÷8
TIFR1  = (1 << ICF1);                    // borrar un evento viejo
TIMSK1 |= (1 << ICIE1);
sei();

/* frecuencia = 2 000 000 / periodo   (16 MHz / 8 = 2 MHz de cuenta) */

El ancho de un pulso

Se captura el flanco ascendente, se cambia el flanco de captura a descendente, y se captura el siguiente:

volatile uint16_t inicio = 0, ancho = 0;
volatile uint8_t  nuevo = 0;

ISR(TIMER1_CAPT_vect) {
    uint16_t t = ICR1;
    if (TCCR1B & (1 << ICES1)) {          // acabó de subir
        inicio = t;
        TCCR1B &= ~(1 << ICES1);        // ahora esperar la bajada
    } else {                               // acabó de bajar
        ancho = t - inicio;
        nuevo = 1;
        TCCR1B |= (1 << ICES1);          // volver a esperar la subida
    }
    TIFR1 = (1 << ICF1);                  // el cambio de flanco puede marcar la bandera: borrarla
}

El último paso no es un detalle: la hoja de datos advierte que cambiar el flanco puede producir una captura espuria, y por eso se borra la bandera después del cambio.

Cuando el pulso es más largo que una vuelta

Con una vuelta de 32,768 ms (÷8), un pulso más largo se confunde. Se extiende el contador a 32 bits contando los desbordamientos (TIMER1_OVF_vect) y se compone (desbordamientos << 16) | ICR1. Hay que tener en cuenta el caso en que el flanco y el desbordamiento ocurren casi a la vez: se verifica la bandera TOV1 junto con el bit alto de la captura.

Una aplicación directa es el sensor ultrasónico HC-SR04: tras un pulso de disparo, el módulo responde con un pulso cuyo ancho es el tiempo de ida y vuelta del sonido. La distancia es ancho × velocidad del sonido / 2, unos 58 µs por centímetro. Con ÷8, ese ancho entra en 16 bits hasta unos 5,6 m, más que el alcance del sensor.

07 · La reina de las bases de tiempo

Una base de tiempo tipo millis

Casi todo programa necesita saber «cuánto pasó». La solución estándar es una base de tiempo: una interrupción periódica que incrementa un contador de milisegundos. El resto del programa lo lee, y compara con una marca:

#include <util/atomic.h>

volatile uint32_t ms = 0;

ISR(TIMER0_COMPA_vect) {               // cada 1 ms (CTC, ÷64, OCR0A = 249: tema 11)
    ms++;
}

uint32_t millis(void) {
    uint32_t copia;
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { copia = ms; }   // 32 bits: lectura protegida
    return copia;
}

/* Uso: una tarea cada 500 ms, sin bloquear */
static uint32_t proxima = 500;
if ((int32_t)(millis() - proxima) >= 0) {
    proxima += 500;                    // avanzar desde la marca anterior: sin deriva
    PORTB ^= (1 << PB5);
}

Tres decisiones de diseño:

  • Ancho del contador. Con 16 bits, el contador da la vuelta a los 65,5 segundos; con 32 bits, a los 49,7 días. Las comparaciones con resta sin signo siguen siendo correctas en la vuelta, pero un plazo mayor que una vuelta no se puede medir.
  • Avanzar desde la marca anterior. proxima += 500 mantiene el ritmo exacto; proxima = millis() + 500 acumula el retraso de cada vuelta del bucle.
  • Costo. La ISR de un contador de 32 bits cuesta unos 60 ciclos, es decir, 0,38 % de la CPU a 1 kHz. Una marca más lenta (por ejemplo 10 ms) cuesta diez veces menos pero resuelve peor.

Esta base de tiempo es el cimiento del esquema cooperativo del tema 42: el bucle principal consulta millis() y ejecuta cada tarea cuando le toca, sin bloquear nunca.

08 · Una decisión de diseño

Contar o medir el período

Para medir una frecuencia hay dos estrategias, y son complementarias: contar los pulsos en una ventana fija (tema 22) o medir el período de un pulso con la captura. La precisión de cada una depende de cuántas cuentas se obtienen:

Comparación para una señal de frecuencia f, con ÷8 (0,5 µs por cuenta) y compuerta de 1 s.
FrecuenciaContando (compuerta de 1 s)Midiendo el período (±1 cuenta)
10 Hz10 cuentas → ±10 %200 000 cuentas: más que una vuelta (usar ÷64 y 25 000 cuentas → ±0,004 %)
100 Hz100 → ±1 %20 000 cuentas → ±0,005 %
1 kHz1000 → ±0,1 %2000 cuentas → ±0,05 %
10 kHz10 000 → ±0,01 %200 cuentas → ±0,5 %
100 kHz100 000 → ±0,001 %20 cuentas → ±5 %

El cruce está donde ambos errores coinciden: para frecuencias bajas conviene medir el período; para altas, contar. Un frecuencímetro completo puede cambiar de método automáticamente según el rango, midiendo el período y calculando f = 1/T por debajo de unos centenares de hercios, y contando por encima.

09 · Analogía con programación

Captura por hardware frente a lectura en la ISR

Este programa simula la medición del período de una señal de 1234 Hz de dos maneras: copiando el contador en el instante exacto del flanco (captura por hardware) y leyéndolo desde una ISR que llega con una latencia variable. Después muestra la resolución del método del período según la frecuencia y convierte anchos de eco ultrasónico en distancias.

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

import random

random.seed(4)
F_CPU = 16_000_000


def medir(frecuencia, preescaler, latencia_us=(0.0, 0.0), flancos=2000):
    """Frecuencias calculadas al medir el período entre flancos ascendentes.
    La captura por hardware no tiene latencia; la lectura dentro de una ISR sí."""
    tick = preescaler / F_CPU
    periodo = 1 / frecuencia
    cuentas = []
    for k in range(flancos):
        instante = k * periodo + random.uniform(*latencia_us) * 1e-6    # cuándo se lee el contador
        cuentas.append(int(instante / tick) % 65536)                     # contador de 16 bits
    medidas = [1 / (((b - a) % 65536) * tick) for a, b in zip(cuentas, cuentas[1:])]
    peor = max(abs(m - frecuencia) / frecuencia * 100 for m in medidas)
    return min(medidas), max(medidas), peor


print("Medir una señal de 1234 Hz (período de 810 µs), 2000 flancos")
for nombre, latencia in (("captura por hardware", (0, 0)),
                         ("ISR con latencia 2-4 µs", (2, 4)),
                         ("ISR con latencia 2-12 µs", (2, 12)),
                         ("ISR con latencia 2-40 µs", (2, 40))):
    bajo, alto, peor = medir(1234, 8, latencia)
    print(f"  {nombre:<26} {bajo:9.2f} a {alto:9.2f} Hz   error máximo {peor:6.3f} %")

print()
print("Resolución al medir el período (±1 cuenta), con ÷8 (0,5 µs por cuenta):")
for f in (50, 100, 1_000, 10_000, 100_000):
    cuentas = F_CPU / 8 / f
    print(f"  {f:>7} Hz -> {cuentas:>9.0f} cuentas por período -> ±{100 / cuentas:7.4f} %")


def distancia_cm(ancho_us, temperatura_c=20.0):
    velocidad = 331.3 + 0.606 * temperatura_c                 # m/s
    return ancho_us * 1e-6 * velocidad / 2 * 100


print()
for ancho in (580, 1000, 5800, 23_000):
    print(f"eco de {ancho:>6} µs -> {distancia_cm(ancho):7.2f} cm   ({ancho / 0.5:>6.0f} cuentas con ÷8)")

Resultado: la captura por hardware da entre 1233,81 y 1234,57 Hz (error máximo 0,046 %, solo cuantización); la lectura en una ISR con latencia variable de 2 a 4 µs ya llega a 0,26 %, con 2 a 12 µs a 1,2 % y con 2 a 40 µs a casi 5 %. La resolución del método del período va de ±0,0025 % a 50 Hz hasta ±5 % a 100 kHz. Probá otras latencias o frecuencias.

El modelo supone una latencia uniformemente repartida; en un programa real depende de qué otras rutinas estén corriendo, y por eso la lectura por software es difícil de acotar.

10 · Exploración interactiva

Captura, base de tiempo y eco

Tres calculadoras: la primera enfrenta la captura por hardware con la lectura en una ISR para una señal y una latencia dadas; la segunda calcula el costo y el alcance de una base de tiempo; la tercera convierte el ancho de un eco ultrasónico en distancia.

Banco de captura y tiempo

1. Captura frente a lectura por software (reloj de 16 MHz)

Cuentas por período—
Frecuencia medida con captura—
Frecuencia medida leyendo TCNT1 en la ISR—
Ancho del pulso en alto—

2. Base de tiempo

Costo en CPU—
El contador da la vuelta cada…—

3. Eco ultrasónico (tipo HC-SR04)

Distancia—
Cuentas capturadas—
Resolución—

La herramienta 1 usa 200 flancos con una latencia repartida uniformemente entre los dos valores. Los anchos se dan como el rango de cuentas posibles por la fase del pulso respecto del contador. La velocidad del sonido de la herramienta 3 es la aproximación lineal 331,3 + 0,606·T m/s.

Recorrido guiado

  1. Con 1234 Hz, ÷8 y latencia de 2 a 12 µs: la captura da un error de unas centésimas de por ciento, y la lectura por software, cerca de 1 %. Dejá la latencia fija en 5 a 5: ahora la lectura por software es casi tan buena, porque la latencia constante no genera jitter.
  2. Subí la frecuencia a 100 000 Hz con ÷1: la captura sigue midiendo bien (160 cuentas por período), pero la lectura por software da resultados disparatados (un error de cientos de por ciento), porque la latencia es comparable al período.
  3. Bajá la frecuencia a 20 Hz con ÷8: el período supera una vuelta (100 000 cuentas) y la herramienta lo avisa. Probá ÷64.
  4. En la base de tiempo, 1 ms con 60 ciclos y 32 bits: 0,375 % de la CPU y un desbordamiento a los 49,7 días. Pasá a 16 bits: 1,1 minutos. Probá 100 µs: el costo sube a 3,75 %.
  5. En el eco, 1000 µs son 17,2 cm. Subí a 23 000 µs: casi 4 m, 46 000 cuentas, cabe en 16 bits con ÷8. Con ÷1 ya no cabe.
Desafío: un frecuencímetro de rango completo

Diseñá un programa que mida cualquier frecuencia de 1 Hz a 100 kHz con un error menor al 0,1 %. Por debajo de unos 500 Hz, medí el período con la captura (con ÷64 llegás hasta 1 Hz o menos); por encima, contá pulsos en una ventana de 1 s (tema 22) o con el contador por hardware en T1. El cambio de método ocurre donde ambos errores se cruzan.

11 · Método de trabajo

Cómo diseñar

  1. Distinguí producir de medir. Producir eventos o señales: comparación. Medir cuándo ocurre algo externo: captura.
  2. Para un período fijo, el modo CTC si necesitás el contador libre de otros usos; el modo normal con OCR += período si querés varios períodos en un temporizador.
  3. Para una señal exacta en un pin, dejá que el hardware lo invierta (COM); no lo hagas desde la ISR.
  4. Para medir un flanco con precisión, usá la captura de entrada (pin ICP1, PB0); no leas el contador en una interrupción externa.
  5. Elegí el prescaler por el rango (que el intervalo entre en una vuelta) y la resolución pedida.
  6. Armá la base de tiempo con 32 bits y protegé las lecturas (tema 21); comparaciones con resta sin signo.
  7. Verificá con un instrumento: un generador de señales conocido y comparación con lo medido; el osciloscopio para ver el pin de comparación.

La captura de entrada trabaja siempre sobre el pin ICP1 (PB0 en la Uno, D8). Si tu diseño lo necesita para otra función, la alternativa es una interrupción de cambio de pin con una lectura de TCNT1, a costa de la precisión que se describió.

12 · Comprensión

Confusiones frecuentes

«La interrupción de comparación se ejecuta en el instante de la coincidencia»

La coincidencia es exacta, pero la interrupción llega con la latencia habitual (decenas de ciclos como mínimo, más si hay otras rutinas). Lo exacto es el cambio del pin por hardware; la ISR es solo un aviso posterior.

«Leer TCNT1 en la ISR de INT0 es lo mismo que la captura»

No: incluye la latencia de la interrupción, que varía de un flanco a otro. La captura guarda el valor en el instante del flanco, antes de que la CPU intervenga.

«Con OCR1A += período se acumula el error»

Al revés: como el próximo valor se calcula desde el anterior programado y no desde la cuenta actual, la latencia de la ISR no se acumula. Tampoco hay deriva por redondeo, porque el período es un número entero de cuentas.

«Se puede medir cualquier ancho de pulso restando dos capturas»

Solo si es menor que una vuelta del contador. Para pulsos más largos hay que contar los desbordamientos o aumentar el prescaler.

«El flanco de captura se cambia sin consecuencias»

Cambiar ICES1 puede marcar ICF1. Hay que borrar la bandera después del cambio (escribiendo un 1) para evitar una captura falsa.

13 · Práctica guiada

Predecí antes de comprobar

1. Un período con OCR +=

Con el temporizador 1 en modo normal y prescaler ÷8 a 16 MHz, ¿qué constante sumás a OCR1A para un evento cada 20 ms?

Ver solución

20 ms / 0,5 µs = 40 000 cuentas. Cabe en 16 bits (el máximo es 65 535, o sea 32,8 ms).

2. Ancho de un pulso

Con ÷8 se captura el flanco ascendente con ICR1 = 1000 y el descendente con ICR1 = 1600. ¿Cuánto dura el pulso?

Ver solución

1600 − 1000 = 600 cuentas × 0,5 µs = 300 µs.

3. Con vuelta del contador

El flanco ascendente se captura en 65 000 y el descendente en 400 (con ÷8). ¿Cuánto dura el pulso?

Ver solución

(400 − 65 000) módulo 65 536 = 936 cuentas × 0,5 µs = 468 µs. La resta sin signo absorbe la vuelta.

4. Un eco ultrasónico

Un sensor entrega un eco de 11 600 cuentas con ÷8 a 20 °C. ¿A qué distancia está el objeto?

Ver solución

11 600 × 0,5 µs = 5800 µs. Distancia = 5800 µs × 343,4 m/s / 2 ≈ 99,6 cm (alrededor de 1 m).

5. Elegir el método

Querés medir una señal de 100 Hz con un error menor que 0,01 %. ¿Contás pulsos en 1 s o medís el período con ÷8? ¿Por qué?

Ver solución razonada

Contando 1 s: 100 cuentas, ±1 % (insuficiente). Midiendo el período: 20 000 cuentas, ±0,005 % (suficiente). Con una señal lenta conviene medir el período con la captura.

14 · Síntesis

Ideas para recordar

  • La comparación produce eventos en un valor de cuenta elegido; la captura anota la cuenta cuando ocurre un evento externo.
  • Los bits COMnx1:0 permiten que el hardware invierta, baje o suba un pin en la coincidencia, sin intervención de la CPU.
  • Con OCRn += período en la ISR se obtienen eventos periódicos sin deriva y varios períodos de un mismo temporizador.
  • La captura de entrada del temporizador 1 (ICP1, ICR1, ICES1) mide con la resolución del contador, sin depender de la latencia.
  • El período se mide restando capturas; el ancho de un pulso cambiando el flanco de captura entre una y otra (borrando ICF1).
  • Una base de tiempo tipo millis es una ISR periódica con un contador de 32 bits, lecturas protegidas y avance desde la marca anterior.
  • Contar es mejor para frecuencias altas; medir el período, para las bajas.

¿Listo para el tema 25?

Antes de avanzar, explicá por qué OCR1A += período no tiene deriva, medí a mano el ancho de un pulso con dos capturas y decidí cómo medirías una señal de 100 Hz y una de 50 kHz. Si podés justificar las tres respuestas, dominás las dos unidades.

En el próximo tema vemos la PWM: ciclo de trabajo, frecuencia y resolución, con los modos rápido y de fase correcta y las salidas por hardware.