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(oOCFnB) enTIFRn; - si la interrupción está habilitada (
OCIEnAenTIMSKn), se ejecuta la ISRTIMERn_COMPA_vect; - según la configuración, el pin de salida
OCnAse 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:
| COMnx1:0 | Acción | Uso típico |
|---|---|---|
00 | El pin funciona como un GPIO común | Comparación solo por bandera o interrupción |
01 | Invertir el pin | Onda cuadrada de frecuencia exacta (como en el tema 22) |
10 | Bajar el pin a 0 | Terminar un pulso |
11 | Subir el pin a 1 | Empezar 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.
| Bit | Registro | Función |
|---|---|---|
ICES1 | TCCR1B, bit 6 | Flanco de captura: 1 = ascendente, 0 = descendente |
ICNC1 | TCCR1B, bit 7 | Cancelador de ruido: exige cuatro muestras iguales antes de capturar (retrasa 4 ciclos) |
ICIE1 | TIMSK1, bit 5 | Habilita la interrupción de captura |
ICF1 | TIFR1, bit 5 | Bandera 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 += 500mantiene el ritmo exacto;proxima = millis() + 500acumula 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:
| Frecuencia | Contando (compuerta de 1 s) | Midiendo el período (±1 cuenta) |
|---|---|---|
| 10 Hz | 10 cuentas → ±10 % | 200 000 cuentas: más que una vuelta (usar ÷64 y 25 000 cuentas → ±0,004 %) |
| 100 Hz | 100 → ±1 % | 20 000 cuentas → ±0,005 % |
| 1 kHz | 1000 → ±0,1 % | 2000 cuentas → ±0,05 % |
| 10 kHz | 10 000 → ±0,01 % | 200 cuentas → ±0,5 % |
| 100 kHz | 100 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)
2. Base de tiempo
3. Eco ultrasónico (tipo HC-SR04)
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
- 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.
- 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.
- Bajá la frecuencia a 20 Hz con ÷8: el período supera una vuelta (100 000 cuentas) y la herramienta lo avisa. Probá ÷64.
- 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 %.
- 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
- Distinguí producir de medir. Producir eventos o señales: comparación. Medir cuándo ocurre algo externo: captura.
- Para un período fijo, el modo CTC si necesitás el contador libre de otros usos; el modo normal con
OCR += períodosi querés varios períodos en un temporizador. - Para una señal exacta en un pin, dejá que el hardware lo invierta (
COM); no lo hagas desde la ISR. - Para medir un flanco con precisión, usá la captura de entrada (pin ICP1, PB0); no leas el contador en una interrupción externa.
- Elegí el prescaler por el rango (que el intervalo entre en una vuelta) y la resolución pedida.
- Armá la base de tiempo con 32 bits y protegé las lecturas (tema 21); comparaciones con resta sin signo.
- 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:0permiten que el hardware invierta, baje o suba un pin en la coincidencia, sin intervención de la CPU. - Con
OCRn += períodoen 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.