← Índice del curso

Microcontroladores · Tema 11

Taller: configurar un periférico con el datasheet

Cierre del bloque 2. Con todo lo visto sobre registros, bits y memoria vamos a configurar un periférico real sin ayuda de bibliotecas: el temporizador 0. Seguimos seis pasos, de la pregunta «¿qué necesito?» al LED que parpadea con la precisión del hardware.

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).

Requisitos del taller.
DatoValor
Reloj de la CPU16 MHz
Base de tiempo deseada1 ms, repetida sin deriva
SalidaLED en PB5, invertido cada 500 ms
RecursosTemporizador 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:

Registros del temporizador 0 y sus bits (del 7 al 0).
RegistroTipoBitsPara qué sirve
TCCR0AControlCOM0A1, COM0A0, COM0B1, COM0B0, –, –, WGM01, WGM00Modo de funcionamiento y comportamiento de los pines de salida
TCCR0BControlFOC0A, FOC0B, –, –, WGM02, CS02, CS01, CS00Resto del modo y selección del reloj (prescaler)
TCNT0Datos8 bitsEl valor actual de la cuenta
OCR0A, OCR0BDatos8 bitsValores de comparación
TIMSK0Control–, –, –, –, –, OCIE0B, OCIE0A, TOIE0Habilitación de interrupciones (no las usamos hoy)
TIFR0Estado–, –, –, –, –, OCF0B, OCF0A, TOV0Banderas 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:

Modos del temporizador 0 (WGM02 está en TCCR0B; WGM01 y WGM00, en TCCR0A).
WGM02:0ModoValor máximo de la cuenta (TOP)
000Normal0xFF; la bandera TOV0 se marca al desbordar
001PWM de fase correcta0xFF
010CTC (clear timer on compare match)OCR0A
011PWM rápido0xFF
101PWM de fase correctaOCR0A
111PWM rápidoOCR0A

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:

Selección del reloj del temporizador 0.
CS02:0Fuente
000Sin reloj: temporizador detenido
001Reloj de la CPU ÷1
010÷8
011÷64
100÷256
101÷1024
110, 111Reloj externo en el pin T0 (flanco descendente o ascendente)

La coincidencia ocurre cada (OCR0A + 1) cuentas, y cada cuenta dura prescaler / f_CPU. Entonces:

período = (OCR0A + 1) · prescaler / f_CPU

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:

Cuentas necesarias para 1 ms con un reloj de 16 MHz.
PrescalerCuentas = 16 000 / prescaler¿Sirve?
÷116 000No: supera 256
÷82 000No
÷64250Sí, y exacto: OCR0A = 249
÷25662,5Solo aproximado (error de 0,8 %)
÷102415,625Solo 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:

  1. Esperar a que OCF0A valga 1: while (!(TIFR0 & (1 << OCF0A))) { }.
  2. 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, TCCR0A debe valer 0x02, TCCR0B 0x03 y OCR0A 249. El decodificador del laboratorio verifica ese tipo de lecturas.
  • La sensibilidad. Cambiá OCR0A a 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íntomas frecuentes en este programa.
SíntomaCausa 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 irregularLa bandera no se borra bien (por ejemplo, con &= ~) y las esperas terminan al instante
El LED nunca enciende pero el programa correFalta DDRB |= (1 << PB5)
El período es 4 veces mayor o menorPrescaler equivocado (÷256 en lugar de ÷64) o reloj distinto de 16 MHz
El período es ligeramente distinto de 1 sOCR0A 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)

Evaluación de los cinco prescalers. La fila resaltada es la mejor opción.
PrescalerCS02:0OCR0APeríodo realError

2. ¿Qué está configurado?

Modo—
Reloj—
Período del contador—
Pines de salida—

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

  1. Con 16 MHz y 1 ms, comprobá la fila resaltada: ÷64, OCR0A = 249, error 0 %. Es el cálculo del paso 3.
  2. 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.)
  3. Pedí 10 µs: aparecen dos opciones exactas (÷1 y ÷8) y gana la de menor prescaler.
  4. 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.
  5. En el decodificador, presioná «Valores del taller» y comprobá que da CTC, ÷64, 1 ms. Después poné 0x00 en TCCR0B: 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:

  1. Definir el requisito en números: frecuencia, período, velocidad, resolución, tensión.
  2. Encontrar el capítulo y la lista de registros; clasificarlos en control, estado y datos.
  3. Elegir el modo de operación entre los que ofrece el periférico, y anotar los bits que lo seleccionan.
  4. Calcular los parámetros (divisores, valores de comparación, velocidades) con las fórmulas de la hoja de datos, buscando valores enteros y exactos.
  5. Escribir la configuración con nombres de bit, en el orden correcto, dejando para el final lo que arranca el periférico.
  6. 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 con OCR0A: período = (OCR0A + 1) · prescaler / f_CPU.
  • Los bits CS02:0 eligen 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.
  • OCF0A se 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.