01 · Punto de partida
Del manual al código
En los temas 9 y 10 aprendiste a leer un registro y a modificar sus bits. Falta la parte más difícil: partir de un objetivo («quiero medir un milisegundo») y llegar a los valores concretos de los registros. Eso es lo que se hace con cada periférico nuevo, y es la habilidad que distingue a quien puede usar cualquier chip de quien solo copia ejemplos.
En este taller usamos el temporizador 0 del ATmega328P. Es un contador de 8 bits que cuenta ciclos de reloj, y uno de los periféricos más útiles: mide tiempo sin ocupar la CPU. Lo estudiaremos con más profundidad en el bloque de temporizadores; aquí lo tomamos como excusa para practicar el método de lectura de la hoja de datos.
Vas a necesitar el tema 9 (registros y banderas que se borran escribiendo un 1), el tema 10 (máscaras y campos) y el parpadeo del tema 5. Todavía no usamos interrupciones: la CPU consulta la bandera del temporizador.
02 · Qué queremos
El objetivo del taller
Queremos que el LED de la placa (PB5) parpadee con un período de exactamente 1 segundo: 500 ms encendido y 500 ms apagado. Con _delay_ms(500) del tema 5 se logra algo parecido, pero con dos inconvenientes: la CPU queda ocupada esperando, y el tiempo del resto del bucle se suma al retardo, de modo que el período real es más largo que el pedido.
La solución es delegar el conteo del tiempo en un periférico que trabaje por su cuenta. Un temporizador configurado para marcar 1 ms exacto, contando 500 marcas por cada cambio del LED, no acumula demora aunque el programa haga otras cosas entre medias (mientras cada vuelta tarde menos de 1 ms).
| Dato | Valor |
|---|---|
| Reloj de la CPU | 16 MHz |
| Base de tiempo deseada | 1 ms, repetida sin deriva |
| Salida | LED en PB5, invertido cada 500 ms |
| Recursos | Temporizador 0, sin interrupciones |
03 · Paso 1
Ubicar el capítulo y los registros
En la hoja de datos del ATmega328P, el capítulo se llama 8-bit Timer/Counter0 with PWM. Al final trae la sección «Register Description». Allí están los siete registros del periférico, que clasificamos como en el tema 9:
| Registro | Tipo | Bits | Para qué sirve |
|---|---|---|---|
TCCR0A | Control | COM0A1, COM0A0, COM0B1, COM0B0, –, –, WGM01, WGM00 | Modo de funcionamiento y comportamiento de los pines de salida |
TCCR0B | Control | FOC0A, FOC0B, –, –, WGM02, CS02, CS01, CS00 | Resto del modo y selección del reloj (prescaler) |
TCNT0 | Datos | 8 bits | El valor actual de la cuenta |
OCR0A, OCR0B | Datos | 8 bits | Valores de comparación |
TIMSK0 | Control | –, –, –, –, –, OCIE0B, OCIE0A, TOIE0 | Habilitación de interrupciones (no las usamos hoy) |
TIFR0 | Estado | –, –, –, –, –, OCF0B, OCF0A, TOV0 | Banderas de eventos, que se borran escribiendo un 1 |
Para este taller usaremos TCCR0A, TCCR0B y OCR0A para configurar, y la bandera OCF0A de TIFR0 para consultar. El valor inicial de todos es 0: el temporizador está detenido y en modo normal.
04 · Paso 2
Elegir el modo de funcionamiento
Los tres bits WGM02:0 (waveform generation mode), repartidos entre ambos registros de control, eligen cómo cuenta el temporizador:
| WGM02:0 | Modo | Valor máximo de la cuenta (TOP) |
|---|---|---|
000 | Normal | 0xFF; la bandera TOV0 se marca al desbordar |
001 | PWM de fase correcta | 0xFF |
010 | CTC (clear timer on compare match) | OCR0A |
011 | PWM rápido | 0xFF |
101 | PWM de fase correcta | OCR0A |
111 | PWM rápido | OCR0A |
El modo normal cuenta de 0 a 255 y vuelve a 0: el período queda fijado por el prescaler y no se puede ajustar. El modo CTC permite elegir el valor máximo: el contador sube hasta que coincide con OCR0A, se marca la bandera OCF0A y vuelve a cero. Así se obtiene el período que se quiera, dentro de los límites del contador. Es el modo apropiado para una base de tiempo.
Modo CTC significa WGM02:0 = 010: el único bit en 1 es WGM01, que está en TCCR0A. Entonces: TCCR0A = (1 << WGM01);. Los bits COM0A y COM0B quedan en 0 para que los pines no se conecten al temporizador.
05 · Paso 3
Elegir el reloj y calcular el valor de comparación
El temporizador cuenta pulsos del reloj de la CPU, pasados por un divisor llamado prescaler. Los bits CS02:0 de TCCR0B lo eligen, y además arrancan y detienen el contador:
| CS02:0 | Fuente |
|---|---|
000 | Sin reloj: temporizador detenido |
001 | Reloj de la CPU ÷1 |
010 | ÷8 |
011 | ÷64 |
100 | ÷256 |
101 | ÷1024 |
110, 111 | Reloj externo en el pin T0 (flanco descendente o ascendente) |
La coincidencia ocurre cada (OCR0A + 1) cuentas, y cada cuenta dura prescaler / f_CPU. Entonces:
Despejando: OCR0A + 1 = período · f_CPU / prescaler. Con 1 ms y 16 MHz, el producto período · f_CPU vale 16 000, y el resultado dividido por cada prescaler tiene que ser un entero entre 1 y 256, porque el contador es de 8 bits:
| Prescaler | Cuentas = 16 000 / prescaler | ¿Sirve? |
|---|---|---|
| ÷1 | 16 000 | No: supera 256 |
| ÷8 | 2 000 | No |
| ÷64 | 250 | Sí, y exacto: OCR0A = 249 |
| ÷256 | 62,5 | Solo aproximado (error de 0,8 %) |
| ÷1024 | 15,625 | Solo aproximado (error de 2,4 %) |
La opción exacta es prescaler ÷64 y OCR0A = 249. Conviene que el cálculo dé un número entero: cualquier fracción se convierte en error sistemático. Que 16 MHz sea múltiplo de 1000 · 64 no es casualidad: por eso los relojes de 16 MHz se prefieren para marcas de milisegundos.
El bit para ÷64 es 011: CS01 y CS00 en 1. Escribir esos bits arranca el temporizador, así que se hace al final de la configuración.
06 · Paso 4
La bandera de coincidencia
Cuando la cuenta coincide con OCR0A, el hardware pone en 1 la bandera OCF0A (bit 1 de TIFR0). Nuestra espera consiste en dos acciones:
- Esperar a que
OCF0Avalga 1:while (!(TIFR0 & (1 << OCF0A))) { }. - Borrarla para detectar la próxima. Es una bandera que se borra escribiendo un 1, como estudiamos en el tema 9, de modo que la forma correcta es la asignación directa:
TIFR0 = (1 << OCF0A);.
Si se usara TIFR0 &= ~(1 << OCF0A), se escribiría un 0 en ese bit, la bandera nunca se borraría y las esperas siguientes terminarían de inmediato. Es el error más frecuente en este tipo de programa.
07 · Paso 5
Escribir el programa
Con las decisiones tomadas, el programa completo es corto:
#define F_CPU 16000000UL
#include <avr/io.h>
static void esperar_1ms(void) {
while (!(TIFR0 & (1 << OCF0A))) { } // esperar la coincidencia
TIFR0 = (1 << OCF0A); // borrar la bandera: se escribe un 1
}
int main(void) {
DDRB |= (1 << PB5); // PB5 como salida
TCCR0A = (1 << WGM01); // modo CTC
OCR0A = 249; // 250 cuentas por período
TCCR0B = (1 << CS01) | (1 << CS00); // prescaler ÷64: el temporizador arranca
while (1) {
for (uint16_t i = 0; i < 500; i++) {
esperar_1ms();
}
PORTB ^= (1 << PB5); // invertir el LED cada 500 ms
}
}
Observá el orden en la inicialización: primero el modo, después el valor de comparación y, por último, el prescaler, porque esa última asignación arranca el contador. Así el temporizador nunca cuenta con una configuración a medias.
No usamos _delay_ms() ni necesitamos la macro F_CPU para los retardos (la definimos igual por costumbre y para otras partes). La precisión del tiempo la da el hardware: cada marca ocurre cada 250 × 64 / 16 000 000 s = 1 ms exacto. Aunque la CPU tarde unos microsegundos en volver a consultar la bandera, el temporizador sigue contando, y la próxima marca llega a su hora.
08 · Paso 6
Verificar
Un programa que «parece funcionar» no está verificado. Comprobá tres cosas:
- El período. Con un osciloscopio o un analizador lógico en PB5 (pin D13), el nivel debe invertirse cada 500 ms: período de la señal cuadrada, 1,000 s. Con un cronómetro de mano alcanza para ver que está en el orden correcto.
- Los registros. En el simulador o el depurador,
TCCR0Adebe valer0x02,TCCR0B0x03yOCR0A249. El decodificador del laboratorio verifica ese tipo de lecturas. - La sensibilidad. Cambiá
OCR0Aa 124: el LED debe parpadear al doble de velocidad (período de 0,5 s). Si no cambia, el temporizador no está funcionando como creías.
Los fallos típicos y su causa:
| Síntoma | Causa probable |
|---|---|
| El LED no parpadea nunca (el programa queda esperando) | No se configuró CS02:0: el temporizador está detenido y la bandera nunca se marca |
| El LED parpadea muy rápido, de forma irregular | La bandera no se borra bien (por ejemplo, con &= ~) y las esperas terminan al instante |
| El LED nunca enciende pero el programa corre | Falta DDRB |= (1 << PB5) |
| El período es 4 veces mayor o menor | Prescaler equivocado (÷256 en lugar de ÷64) o reloj distinto de 16 MHz |
| El período es ligeramente distinto de 1 s | OCR0A mal calculado (se olvidó el «+1» de las cuentas) |
09 · Analogía con programación
Buscar la configuración y ver la deriva
Este programa en Python hace dos cosas. Primero, automatiza el paso 3: dado un período, prueba los cinco prescalers y lista los que alcanzan con un contador de 8 bits, con su error. Después compara dos formas de medir 500 ms: con un retardo fijo más el trabajo de la vuelta, y con un temporizador que marca cada milisegundo, con la bandera de un solo bit que se pierde si no se atiende a tiempo.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
import math
F_CPU = 16_000_000
PRESCALERS = {1: 0b001, 8: 0b010, 64: 0b011, 256: 0b100, 1024: 0b101}
def buscar(periodo_s):
"""Opciones (prescaler, CS02:0, OCR0A, error %) que logran el período pedido en modo CTC."""
opciones = []
for n, cs in PRESCALERS.items():
cuentas = int(periodo_s * F_CPU / n + 0.5) # OCR0A + 1
if 1 <= cuentas <= 256:
real = cuentas * n / F_CPU
opciones.append((abs(real - periodo_s) / periodo_s, n, cs, cuentas - 1))
return sorted(opciones)
for periodo in (1e-3, 500e-6, 10e-6):
print(f"Período {periodo * 1e6:g} µs:")
opciones = buscar(periodo)
for error, n, cs, ocr in opciones:
print(f" /{n:<4} CS02:0={cs:03b} OCR0A={ocr:<3} error={error * 100:.2f} %")
if not opciones:
print(" ninguna opción: el contador de 8 bits no alcanza")
print()
# ¿Qué pasa si el programa además hace un trabajo en cada vuelta?
def con_retardo(trabajo_ms, vueltas):
return vueltas * (trabajo_ms + 1.0) # trabajo + _delay_ms(1)
def con_temporizador(trabajo_ms, vueltas):
t, ya_atendidos, perdidos = 0.0, 0, 0
for _ in range(vueltas):
t += trabajo_ms
coincidencias = math.floor(t + 1e-9) # la bandera se marca 1 vez por ms
if coincidencias > ya_atendidos: # ya hay una pendiente: no se espera
perdidos += coincidencias - ya_atendidos - 1
ya_atendidos = coincidencias
else: # se espera a la próxima coincidencia
ya_atendidos += 1
t = float(ya_atendidos)
return t, perdidos
for trabajo in (0.3, 1.5):
r = con_retardo(trabajo, 500)
t, perdidos = con_temporizador(trabajo, 500)
print(f"trabajo {trabajo} ms por vuelta -> 500 vueltas: con _delay_ms {r:.0f} ms, "
f"con temporizador {t:.0f} ms (ticks perdidos: {perdidos})")
Resultado: para 1 ms solo ÷64 es exacto (OCR0A = 249); para 10 µs sirven ÷1 y ÷8. Con 0,3 ms de trabajo por vuelta, el retardo acumula 650 ms contra los 500 ms del temporizador. Con 1,5 ms de trabajo (más que la marca), el temporizador pierde ticks: ya no puede sostener el ritmo. Cambiá el trabajo a 0,9 y a 1,0 para ver el límite.
El modelo ilustra dos ideas de diseño. Primera: un temporizador elimina la deriva porque su cuenta no depende de lo que haga la CPU. Segunda: tiene un límite, el trabajo por vuelta debe ser menor que el período, y cuando no lo es, la bandera sola no alcanza. Las interrupciones, que veremos en el bloque 5, resuelven ese segundo problema contando cada marca.
10 · Exploración interactiva
Calculadora y decodificador del temporizador 0
La primera herramienta automatiza los pasos 2 y 3: dado el reloj y el período deseado, evalúa los cinco prescalers y entrega el código. La segunda hace el camino inverso, el del paso 6: dados los valores de los registros, dice qué modo, qué reloj y qué período representan.
Banco del temporizador 0
1. Calcular la configuración (modo CTC)
| Prescaler | CS02:0 | OCR0A | Período real | Error |
|---|
2. ¿Qué está configurado?
Los valores se admiten en decimal, 0x… o 0b…. La calculadora elige la opción de menor error y, en caso de empate, la de menor prescaler (mejor resolución). El decodificador calcula el período del contador; en los modos de PWM esa cifra es el período de la señal, no una cuenta comparable con la del modo CTC.
Recorrido guiado
- Con 16 MHz y 1 ms, comprobá la fila resaltada: ÷64, OCR0A = 249, error 0 %. Es el cálculo del paso 3.
- Pasá el reloj a 8 MHz: ¿qué prescaler sirve para 1 ms ahora? (÷8 con OCR0A = 999 no entra; ÷1 tampoco; ÷64 da 125 cuentas, OCR0A = 124.)
- Pedí 10 µs: aparecen dos opciones exactas (÷1 y ÷8) y gana la de menor prescaler.
- Pedí 1 s: ninguna opción alcanza. Es el límite de un contador de 8 bits y por eso existen el temporizador 1 y las cuentas por software.
- En el decodificador, presioná «Valores del taller» y comprobá que da CTC, ÷64, 1 ms. Después poné
0x00enTCCR0B: el temporizador figura detenido, el síntoma del primer fallo de la tabla del paso 6.
Desafío: leer una configuración ajena
Alguien te pasa estos valores: TCCR0A = 0x83, TCCR0B = 0x01. Sin ejecutar el decodificador, deducí el modo y el reloj. 0x83 = 1000 0011 pone COM0A1 y WGM01:0 = 11; 0x01 deja WGM02 = 0 y CS = 001. Es PWM rápido, sin prescaler, con el pin OC0A conectado: 62,5 kHz a 16 MHz. Verificalo con la herramienta.
11 · Método de trabajo
El método, para cualquier periférico
Lo que hicimos con el temporizador 0 vale para cualquier otro periférico de cualquier chip. Los seis pasos son:
- Definir el requisito en números: frecuencia, período, velocidad, resolución, tensión.
- Encontrar el capítulo y la lista de registros; clasificarlos en control, estado y datos.
- Elegir el modo de operación entre los que ofrece el periférico, y anotar los bits que lo seleccionan.
- Calcular los parámetros (divisores, valores de comparación, velocidades) con las fórmulas de la hoja de datos, buscando valores enteros y exactos.
- Escribir la configuración con nombres de bit, en el orden correcto, dejando para el final lo que arranca el periférico.
- Verificar con instrumentos y con pruebas de sensibilidad (cambiar un parámetro y ver que el resultado cambia como se espera).
Cuando algo falla, empezá por los registros: leelos con el depurador y compará con lo que querías. La mayoría de los problemas con periféricos son un bit mal puesto, una bandera mal borrada o un reloj que nunca se encendió.
Con este taller termina el bloque 2. Ya sabés cómo ejecuta la CPU, dónde viven los datos, cómo se leen los registros y cómo se configura un periférico. En el bloque 3 vamos a ocuparnos del reloj que marca el ritmo de todo: de dónde sale, cómo se elige y qué pasa cuando falla.
12 · Comprensión
Confusiones frecuentes
«OCR0A = 250 da 250 cuentas»
El contador va de 0 a OCR0A inclusive: son OCR0A + 1 cuentas. Para 250 cuentas hace falta OCR0A = 249. Olvidar el «+1» produce un error del 0,4 % en este caso, suficiente para que un reloj atrase.
«Configurar el prescaler primero es lo mismo»
Escribir los bits CS arranca el temporizador. Si el modo o el valor de comparación todavía son los del reset, el contador cuenta con una configuración equivocada y puede marcar una coincidencia falsa. Conviene arrancarlo al final.
«Con _delay_ms ya tengo tiempos precisos»
Mide el retardo, pero no el tiempo que tarda el resto del bucle, que se le suma. Además ocupa la CPU. Un temporizador es independiente del código y no acumula deriva mientras el trabajo de cada vuelta sea menor que su período.
«El temporizador de 8 bits sirve para cualquier período»
Con 16 MHz, el máximo es 256 × 1024 ÷ 16 MHz ≈ 16,4 ms. Para períodos mayores se cuentan varias marcas (como los 500 ms de este taller) o se usa un temporizador de 16 bits.
«Si el programa compila, el temporizador está bien configurado»
El compilador no sabe qué significan los bits. Un bit equivocado compila sin problemas. Hay que verificar con instrumentos o con el depurador.
13 · Práctica guiada
Predecí antes de comprobar
1. Dos milisegundos
Con un reloj de 16 MHz, ¿qué prescaler y qué OCR0A producen una marca de 2 ms en modo CTC?
Ver solución
2 ms × 16 MHz = 32 000 ciclos. Con ÷64 serían 500 cuentas (no entra); con ÷256, 125 cuentas: exacto. Resultado: prescaler ÷256 (CS02 en 1) y OCR0A = 124.
2. Leer una configuración
En un ATmega328P a 16 MHz, TCCR0A = 0x02, TCCR0B = 0x04 y OCR0A = 124. ¿Qué período tiene la marca?
Ver solución
Modo CTC (WGM01 = 1), prescaler ÷256 (CS02:0 = 100). Período = 125 × 256 / 16 000 000 = 2 ms.
3. El LED que no parpadea
Un programa configura TCCR0A = (1 << WGM01); y OCR0A = 249; pero no escribe TCCR0B. Se queda en la espera de la bandera para siempre. ¿Por qué?
Ver solución
Tras el reset, TCCR0B vale 0, es decir CS02:0 = 000: el temporizador no recibe reloj y no cuenta. La coincidencia nunca ocurre y OCF0A nunca se marca.
4. El LED que parpadea demasiado rápido
En lugar de TIFR0 = (1 << OCF0A); el programa usa TIFR0 &= ~(1 << OCF0A);. ¿Qué ocurre y por qué?
Ver solución
Esa expresión escribe un 0 en OCF0A, y escribir 0 en una bandera que se borra con un 1 no hace nada. Después de la primera coincidencia la bandera queda en 1 para siempre y esperar_1ms() retorna de inmediato: las 500 «esperas» se cumplen en microsegundos y el LED parpadea a decenas de kilohertz (apenas visible como brillo tenue).
5. El máximo del temporizador
¿Cuál es el período máximo que se obtiene con el temporizador 0 a 16 MHz y cómo se logra?
Ver solución
Prescaler ÷1024 y 256 cuentas (OCR0A = 255): 256 × 1024 / 16 000 000 = 16,384 ms. Para tiempos mayores se acumulan marcas por software o se usa el temporizador 1.
14 · Síntesis
Ideas para recordar
- Configurar un periférico es un método: requisito, registros, modo, cálculo, código y verificación.
- El modo CTC (
WGM02:0 = 010) permite elegir el período conOCR0A: período = (OCR0A + 1) · prescaler / f_CPU. - Los bits
CS02:0eligen el reloj; con 000 el temporizador está detenido, y escribirlos lo arranca. - Se busca una combinación que dé un entero exacto entre 1 y 256 cuentas.
OCF0Ase marca al coincidir y se borra escribiendo un 1 con asignación directa.- Un temporizador no acumula deriva mientras el trabajo por vuelta sea menor que su período.
- Se verifica con instrumentos, con el depurador y con pruebas de sensibilidad.
¿Listo para el bloque 3?
Antes de avanzar, calculá a mano el prescaler y el OCR0A para otro período, escribí la configuración con nombres de bit y explicá por qué se escribe TCCR0B al final. Si podés justificar las tres respuestas, ya sabés configurar un periférico con la hoja de datos.
En el próximo tema empieza el bloque de reloj y arranque con el reloj del sistema: osciladores internos y externos, cristales, prescalers y qué determina la velocidad de todo lo que vimos.