Periféricos
Los periféricos son hardware que trabaja solo, en paralelo con la CPU. Un temporizador cuenta mientras el programa hace otra cosa; un módulo PWM sostiene una salida sin que nadie lo atienda. Entenderlos cambia la manera de escribir programas: se deja de esperar.
01Dejar de esperar
El primer programa de cualquiera hace parpadear un LED con un retardo. El problema aparece con el segundo requerimiento: mientras espera, el programa no puede hacer nada más. Ni leer un pulsador, ni atender el puerto serie, ni medir una temperatura.
// Así NO: mientras dura el retardo, el micro está preso
while (1) {
led_on(); _delay_ms(500);
led_off(); _delay_ms(500); // un pulsador apretado acá no se detecta
}
// Así SÍ: el temporizador cuenta solo y avisa; el lazo queda libre
while (1) {
if (bandera_500ms) { bandera_500ms = 0; led_toggle(); }
atender_pulsadores();
atender_puerto_serie();
}
El segundo esquema —lazo principal que nunca se bloquea más banderas que ponen las interrupciones— es la estructura de casi todo programa embebido serio.
| Periférico | Qué hace solo | Ejemplo típico |
|---|---|---|
| Temporizador / contador | Cuenta pulsos de reloj o eventos externos | Base de tiempo, medir frecuencia, generar retardos |
| PWM | Sostiene una onda cuadrada de ciclo de trabajo ajustable | Brillo de un LED, velocidad de un motor, servo |
| Captura (CCP) | Guarda el valor del contador cuando llega un flanco | Medir el ancho de un pulso, un período, un sensor ultrasónico |
| Comparación | Dispara un evento cuando el contador llega a un valor | Interrupción periódica exacta, generar frecuencias |
| Conversor A/D | Convierte una tensión en un número | Sensores, potenciómetros, medición de batería |
| USART, SPI, I²C | Arma y desarma tramas | Módulo de comunicación |
| Watchdog | Vigila que el programa no se cuelgue | Seguridad en equipos que quedan solos |
02Temporizadores
Un temporizador es un contador binario —lo mismo que se vio en contadores programables— alimentado por el reloj del sistema a través de un preescaler, que es simplemente un divisor.
Cristal de 16 MHz, temporizador de 8 bits en modo CTC:
- Con preescaler 64, cada cuenta dura 64/16 000 000 = 4 µs.
- Para 1 ms hacen falta 1000/4 = 250 cuentas.
- Se carga OCR = 250 − 1 = 249. Como entra en 8 bits (máximo 255), sirve.
- Verificación: (249 + 1) × 4 µs = 1000 µs exactos. Sin error, porque 16 MHz es múltiplo de 64 × 1000.
Con preescaler 8 harían falta 2000 cuentas: no entra en 8 bits y hay que usar el temporizador de 16 bits. Ese es el razonamiento completo: elegir el preescaler más chico cuyo resultado entre en el contador, porque cuanto menor es el preescaler, mejor es la resolución.
- Normal: cuenta hasta el máximo y desborda. Simple, pero el período queda fijo por el preescaler.
- CTC (comparar y borrar): cuenta hasta OCR y se reinicia. Es el modo de las bases de tiempo exactas.
- Fast PWM y PWM de fase correcta: la salida cambia sola según el valor de comparación. Ver la sección de PWM.
- Contador de eventos externos: en vez del reloj interno cuenta flancos en una pata. Es un frecuencímetro con dos líneas de código.
03Interrupciones
Una interrupción es un desvío automático: cuando el periférico levanta su bandera, la CPU termina la instrucción en curso, guarda dónde estaba y salta a una rutina especial. Al terminar, vuelve exactamente al mismo lugar. El programa principal ni se entera.
- Respuesta inmediata a un evento, sin estar preguntando.
- El lazo principal queda libre.
- Permite dormir el micro y despertarlo sólo cuando pasa algo.
- Cortísima. Poner una bandera, leer un dato, y salir. Nada de retardos, cálculos largos ni mandar caracteres por el puerto serie.
- Las variables que comparte con el lazo principal se declaran volatile, o el compilador las optimiza y el programa deja de ver los cambios.
- Si la variable compartida es de más de un byte, hay que leerla con las interrupciones deshabilitadas: si no, se puede leer la mitad vieja y la mitad nueva.
- La bandera se limpia sola o hay que limpiarla a mano según el periférico: si no se limpia, la interrupción se dispara para siempre.
// Base de tiempo de 1 ms con el temporizador en CTC
volatile uint16_t ms = 0; // volatile: la modifica una interrupción
ISR(TIMER0_COMPA_vect) { // se ejecuta cada 1 ms
ms++; // y nada más: la ISR termina acá
}
int main(void) {
TCCR0A = (1 << WGM01); // modo CTC
TCCR0B = (1 << CS01) | (1 << CS00); // preescaler 64
OCR0A = 249; // 250 cuentas de 4 µs = 1 ms
TIMSK0 = (1 << OCIE0A); // habilita la interrupción
sei(); // habilita globalmente
while (1) { /* el lazo queda libre para todo lo demás */ }
}
Entre que ocurre el evento y que empieza la ISR pasan algunos microsegundos: la CPU termina la instrucción en curso y guarda el contexto. Si además hay otra interrupción ejecutándose, hay que esperarla. Por eso las ISR largas arruinan la respuesta de todo el sistema: una que tarda 2 ms hace perder pulsos a todas las demás. En los micros chicos las prioridades son fijas y están en la tabla de vectores de la hoja de datos.
04PWM: modulación por ancho de pulso
Una salida que conmuta rápido entre 0 y VCC, con el tiempo en alto ajustable. El valor medio es proporcional a ese tiempo, y con eso se regula potencia sin disipar: el transistor está saturado o cortado, nunca en el medio.
| Aplicación | Frecuencia adecuada | Por qué |
|---|---|---|
| Brillo de un LED | > 100 Hz | Por debajo se ve el parpadeo, sobre todo al mover la vista. |
| Motor de continua | 1 a 20 kHz | Por debajo de 20 kHz se escucha el zumbido; por encima, las pérdidas de conmutación crecen. |
| Calefactor o carga térmica | < 10 Hz | La inercia térmica promedia sola: no hace falta conmutar rápido. |
| Servomotor | 50 Hz exactos | No regula por valor medio: el ángulo lo fija el ancho del pulso, entre 1 y 2 ms. |
| Salida analógica (con filtro R-C) | Alta, y filtro lento | Cuanto más alta la frecuencia, más chico el rizado que queda. |
Cristal de 16 MHz, preescaler 8 → cada cuenta dura 0,5 µs.
- Período de 20 ms (50 Hz) = 20 000 µs / 0,5 = 40 000 cuentas: entra justo en 16 bits.
- Pulso de 1 ms (0°) = 2000 cuentas. Pulso de 2 ms (180°) = 4000 cuentas.
- Cada grado son (4000 − 2000)/180 = 11,1 cuentas: hay resolución de sobra.
Con el temporizador de 8 bits esto no se puede hacer: no alcanzan las cuentas para 20 ms con resolución útil. Elegir el temporizador correcto es parte del problema.
05Captura y comparación
Cuando llega un flanco a una pata, el módulo copia el valor del contador en un registro y avisa. La CPU no tiene que estar mirando: el instante queda registrado con la precisión del temporizador.
Con dos capturas —flanco de subida y de bajada— se mide el ancho de un pulso. Es la manera correcta de leer un sensor ultrasónico HC-SR04, un receptor de control remoto o la señal de un caudalímetro.
Al revés: cuando el contador llega a un valor cargado, dispara un evento —interrupción, cambio de una pata, reinicio del contador—. Es lo que hay debajo del modo CTC y del PWM.
El sensor devuelve un pulso cuyo ancho es proporcional al tiempo de ida y vuelta del sonido.
- Con preescaler 8 a 16 MHz, cada cuenta son 0,5 µs.
- Se capturan los dos flancos y se restan: si la diferencia es de 2320 cuentas, el pulso duró 2320 × 0,5 = 1160 µs.
- El sonido viaja a 343 m/s: recorrió 0,001160 × 343 = 0,398 m, ida y vuelta. La distancia es la mitad: 19,9 cm.
Midiendo lo mismo con un lazo de espera, cualquier interrupción que ocurra en el medio arruina la medición. Con captura, no.
06El conversor A/D interno
Casi siempre es de aproximaciones sucesivas, de 10 o 12 bits, con un multiplexor que permite elegir entre varias patas. Todo lo conceptual está en conversores A/D y D/A; acá interesan los cuatro parámetros que hay que configurar.
| Qué se elige | Detalle |
|---|---|
| Canal | Qué pata se mide. El multiplexor es uno solo: se mide un canal por vez, y hay que esperar entre cambios. |
| Referencia | La alimentación, una referencia interna (1,1 V típica) o una externa por AREF. Define la escala completa y con ella la resolución. |
| Reloj del conversor | Con preescaler propio: debe quedar entre 50 y 200 kHz para la máxima exactitud. A 16 MHz con preescaler 128 da 125 kHz, y una conversión tarda unos 104 µs. |
| Alineación y disparo | Si el resultado se alinea a izquierda (útil para leer sólo 8 bits) y si la conversión arranca por software o por un evento del temporizador. |
- Referencia sucia: si se usa VCC como referencia y la alimentación tiene rizado, la lectura fluctúa aunque la entrada esté quieta. Referencia interna o externa filtrada.
- Fuente de alta impedancia: el capacitor del sample & hold no llega a cargarse. La hoja de datos pide menos de 10 kΩ; con más, hace falta un seguidor con operacional.
- Ruido digital: conmutaciones del propio micro acopladas a la entrada. Se promedia, se separan las masas y, en los micros que lo tienen, se usa el modo de reducción de ruido que duerme la CPU durante la conversión.
07En el laboratorio
Configurar el temporizador en CTC para una interrupción cada 1 ms y conmutar una pata en cada interrupción. Medir con el osciloscopio: deben salir 500 Hz exactos. Después cambiar el preescaler sin cambiar OCR y ver cómo se corre la frecuencia. Con eso queda clara la fórmula.
Escribir el mismo parpadeo de dos maneras: con _delay_ms() y con la base de tiempo por
interrupción. En las dos, agregar un pulsador que encienda otro LED. Con el retardo, el pulsador
responde tarde o no responde; con la interrupción, responde siempre. Es la práctica que justifica todo
el eje.
Generar PWM a 1 kHz y variar el ciclo de trabajo con un potenciómetro leído por el A/D. Medir con el osciloscopio el ancho del pulso y, con el multímetro en continua, el valor medio. Verificar que Vmed = D × VCC. Después agregar un filtro R-C y comparar la salida filtrada con la cruda.
Con el temporizador de 16 bits, mover un servo de 0° a 180° según el potenciómetro. Medir el ancho del pulso en los extremos y verificar que sean 1 y 2 ms. Anotar qué pasa si la frecuencia no es de 50 Hz.
Generar un pulso de ancho conocido con un 555 y medirlo con el módulo de captura. Comparar con la medición del osciloscopio. Repetir agregando una interrupción que consuma tiempo: la medición por captura no cambia, y ese es el punto.
08Errores frecuentes
| Síntoma | Causa habitual |
|---|---|
| La interrupción se dispara sin parar | No se limpió la bandera del periférico, o se la limpió mal. |
| El programa principal no ve el cambio de una variable | Falta volatile: el compilador la guardó en un registro y no la relee. |
| Un contador de 16 bits da valores absurdos de vez en cuando | Se lo leyó mientras la interrupción lo modificaba. Hay que leerlo con interrupciones deshabilitadas. |
| El sistema responde lento o pierde pulsos | Una ISR demasiado larga. Sacar todo lo que no sea imprescindible al lazo principal. |
| El tiempo del temporizador no da lo calculado | Se olvidó el «−1» del registro de comparación, o el reloj real no es el supuesto (fuses, divisor por 8 activado). |
| El LED con PWM parpadea de manera visible | Frecuencia demasiado baja: subirla por encima de 100 Hz. |
| El motor con PWM chilla | Frecuencia dentro de la banda audible. Llevarla por encima de 18 a 20 kHz. |
| El servo tiembla o no llega a los extremos | Período distinto de 20 ms, o anchos fuera del rango del modelo. Cada servo tiene el suyo. |
| La lectura del A/D salta varias cuentas | Referencia con rizado, fuente de alta impedancia o ruido. Promediar y revisar las tres causas. |
| Al cambiar de canal, la primera lectura está mal | Falta esperar el tiempo de adquisición después de mover el multiplexor. Se descarta la primera conversión. |
09Autoevaluación
¿Qué ventaja concreta tiene un temporizador frente a un lazo de retardo?
Que cuenta solo, en paralelo con la CPU. El programa no queda bloqueado y puede atender otras cosas; además, el tiempo no depende de lo que el programa esté haciendo.
Con 8 MHz y preescaler 256, ¿cuánto dura una cuenta y cuánto un desborde de 8 bits?
Cada cuenta dura 256/8 000 000 = 32 µs; el desborde ocurre cada 256 × 32 = 8,192 ms.
Se quiere una interrupción cada 2 ms con 16 MHz y preescaler 64. ¿Qué valor de comparación?
Cada cuenta son 4 µs; 2000/4 = 500 cuentas → OCR = 499. Como no entra en 8 bits, hay que usar el temporizador de 16 bits (o preescaler 256, con OCR = 124).
¿Por qué una variable compartida con una ISR se declara volatile?
Porque el compilador, al no ver que nadie más la modifica, puede guardarla en un
registro y no volver a leerla de memoria. volatile le prohíbe esa optimización y obliga
a leerla cada vez.
¿Qué debe hacer —y qué no— una rutina de atención de interrupción?
Debe: leer el dato o poner una bandera y salir. No debe: usar retardos, hacer cálculos largos, mandar por el puerto serie ni refrescar el watchdog.
Con PWM de 8 bits, preescaler 8 y 16 MHz, ¿cuál es la frecuencia?
f = 16 000 000/(8 × 256) = 7812,5 Hz. Sirve para un motor —está por debajo de lo audible molesto pero conmuta rápido— y de sobra para un LED.
¿Por qué un servo no se controla por valor medio?
Porque su electrónica interna mide el ancho del pulso, no el promedio: entre 1 y 2 ms dentro de un período de 20 ms. Dos señales con el mismo valor medio y distinto ancho dan ángulos distintos.
¿Para qué sirve el módulo de captura?
Para registrar el instante exacto en que llega un flanco, copiando el valor del contador sin intervención de la CPU. Con dos capturas se mide el ancho de un pulso o un período sin que otras tareas afecten la medición.
Se mide una tensión que varía muy poco y la lectura del A/D salta ±3 cuentas. ¿Qué se revisa?
La referencia (rizado de la alimentación), la impedancia de la fuente (demasiado alta para el sample & hold) y el ruido digital acoplado. Además conviene promediar varias lecturas.
¿Por qué conviene descartar la primera conversión después de cambiar de canal?
Porque el capacitor de retención todavía tiene la carga del canal anterior y necesita un tiempo de adquisición para tomar el valor nuevo. La primera lectura queda contaminada por la medición previa.