01 · Punto de partida
De la caja negra al diagrama de bloques
Hasta ahora tratamos al microcontrolador como un chip que ejecuta un programa. Para controlarlo con precisión, y entender después registros, interrupciones y temporizadores, hace falta saber quién habla con quién dentro del encapsulado y por dónde viajan los datos.
Si programás, ya conocés un modelo parecido: un proceso que lee y escribe direcciones de memoria. La novedad es que, en un microcontrolador, algunas de esas direcciones no son almacenamiento sino hardware: leerlas consulta un pin, escribirlas lo acciona.
Retomamos el diagrama de bloques del tema 1 y los núcleos del tema 2. Ahora veremos cómo se conectan sus partes.
02 · Los participantes
CPU, memorias y periféricos
CPU
Busca instrucciones, las interpreta y las ejecuta. Es el único bloque que «decide». Usa registros internos para operar con datos y una unidad aritmético-lógica (ALU) para calcular.
Memorias
Guardan el programa y los datos. La Flash conserva el código sin energía; la SRAM guarda las variables mientras hay alimentación; la EEPROM retiene datos pequeños que se modifican de vez en cuando.
Periféricos
Bloques de hardware con una función específica: puertos de entrada y salida, temporizadores, conversor analógico, puertos serie. Se configuran y se leen mediante registros.
Buses
Los caminos compartidos por los que viajan direcciones, datos y señales de control entre los tres anteriores.
Podés pensar en la CPU como un cliente que hace pedidos, y en memorias y periféricos como servidores que responden. La CPU nunca toca un pin directamente: pide una lectura o una escritura en una dirección, y el bloque dueño de esa dirección responde.
03 · Los caminos
Direcciones, datos y control
Un bus es un conjunto de líneas eléctricas que se usan juntas. En un acceso típico participan tres grupos de líneas:
- Bus de direcciones. La CPU indica a qué posición quiere acceder. Con n líneas se distinguen 2n direcciones distintas.
- Bus de datos. Transporta el valor leído o escrito. Su ancho suele coincidir con el de la CPU: 8 bits en un AVR, 32 en un Cortex-M.
- Bus de control. Indica si el acceso es de lectura o de escritura y cuándo es válido.
| Líneas (n) | Direcciones (2ⁿ) | Dónde se ve |
|---|---|---|
| 8 | 256 bytes | Microcontroladores muy pequeños |
| 11 | 2 KB | Tamaño de la SRAM del ATmega328P: sus 2048 bytes necesitan 11 líneas |
| 14 | 16 384 posiciones | Contador de programa del ATmega328P: 16 K palabras de 16 bits = 32 KB |
| 16 | 64 KB | Espacio de datos de la mayoría de los AVR |
| 20 | 1 MB | Microcontroladores medianos |
| 32 | 4 GB | Espacio de direcciones de un Cortex-M |
Un detalle práctico: el espacio direccionable es un máximo teórico. Un chip con 32 bits de dirección no tiene 4 GB de memoria física; usa unas pocas regiones de ese espacio para su Flash, su SRAM y sus periféricos, y deja el resto sin usar.
Si ya trabajaste con memorias en el curso anterior, esto te resulta familiar: las líneas de dirección entraban en un decodificador que activaba un chip distinto según el rango. Dentro del microcontrolador sucede lo mismo, solo que integrado.
04 · Dos formas de conectar
Von Neumann frente a Harvard
La diferencia está en cuántos caminos hay entre la CPU y la memoria. En la arquitectura von Neumann, programa y datos comparten memoria y bus. En la arquitectura Harvard, hay memorias y buses separados.
El AVR es de tipo Harvard: la Flash de programa tiene su propio espacio de direcciones, distinto del de datos (registros, E/S y SRAM). Los núcleos Cortex-M tienen varios buses internos separados, pero ofrecen a los programas un único espacio de direcciones; por eso se los llama de tipo Harvard modificado.
Consecuencias prácticas
- En un Harvard, la CPU puede buscar la siguiente instrucción mientras opera con un dato. Esto ayuda a ejecutar más instrucciones por ciclo.
- En un AVR, una constante guardada en Flash no se lee como una variable de SRAM: hay instrucciones especiales (
LPM) y macros del compilador, comoPROGMEM. Si no las usás, el compilador copia las constantes a la SRAM al arrancar y consume los escasos 2 KB. - En una arquitectura von Neumann resulta simple tratar el código como datos, por ejemplo para cargar un programa en memoria y ejecutarlo.
05 · El bloque que decide
Por dentro de la CPU
Una CPU, por simple que sea, tiene los mismos componentes que viste como piezas sueltas en los cursos anteriores: registros hechos con flip-flops, una ALU hecha de puertas y una máquina de estados que lo coordina.
| Componente | Qué guarda o hace | Equivalente conocido |
|---|---|---|
| Contador de programa (PC) | Dirección de la próxima instrucción a buscar | Un contador que avanza con cada instrucción y puede cargarse en un salto |
| Registros de trabajo | 32 registros de 8 bits (R0 a R31) para operar con datos | Registros de almacenamiento |
| ALU | Suma, resta, operaciones lógicas y desplazamientos | Sumador y puertas lógicas |
| Registro de estado (SREG) | Banderas: cero, acarreo, negativo, habilitación de interrupciones | Flip-flops con el resultado de la última operación |
| Puntero de pila (SP) | Dirección de la cima de la pila, usada en llamadas y en interrupciones | Un contador que apunta a SRAM |
| Decodificador y unidad de control | Interpreta cada instrucción y activa las señales internas | Máquina de estados y decodificadores |
Cada instrucción pasa por tres etapas conocidas: buscar (leer su código en la memoria de programa), decodificar (saber qué hacer) y ejecutar (operar o acceder a datos). Con una arquitectura Harvard, el AVR superpone la ejecución de una instrucción con la búsqueda de la siguiente, una técnica sencilla de segmentación (pipeline).
06 · Dónde viven las cosas
Las tres memorias
| Memoria | Tamaño | Se conserva sin energía | Qué guarda |
|---|---|---|---|
| Flash | 32 KB | Sí | El programa y las constantes |
| SRAM | 2 KB | No | Variables, pila y datos temporales |
| EEPROM | 1 KB | Sí | Configuración y datos que deben sobrevivir al apagado |
Los tres tamaños son tan chicos que condicionan el diseño: un arreglo de 1000 enteros de 16 bits ocupa 2000 bytes, es decir, casi toda la SRAM. En un microcontrolador se programa con la memoria en mente.
07 · La idea central
Periféricos mapeados en memoria
Cada periférico tiene uno o más registros: posiciones que la CPU puede leer o escribir y que están conectadas a hardware. En la mayoría de los microcontroladores, esos registros ocupan direcciones del mismo espacio que la SRAM. A esto se le llama E/S mapeada en memoria.
Cuando el programa escribe en la dirección de PORTB, los bits del valor llegan a los flip-flops que manejan los pines del puerto B. Cuando lee PINB, obtiene el nivel que tienen los pines en ese instante. No hay «instrucción para encender un LED»: hay una instrucción para escribir un valor en una dirección.
Así lo expresa el lenguaje C. Las dos líneas siguientes son equivalentes en un ATmega328P; la primera es la forma cómoda y la segunda muestra la dirección real:
PORTB = 0x20; // encender PB5 mediante el nombre del registro
*(volatile uint8_t *)0x25 = 0x20; // lo mismo, escribiendo directamente la dirección 0x25
El nombre PORTB es una macro de la biblioteca del fabricante que se expande a esa dirección. La palabra volatile avisa al compilador de que el valor puede cambiar por causas externas al programa y de que cada acceso debe realizarse de verdad; el tema 10 explica por qué es imprescindible.
Un registro no es una variable
Una variable guarda lo que le escribiste. Un registro de periférico puede comportarse distinto: leer PINB no devuelve lo último que escribiste sino el estado de los pines; algunos bits se ponen en 1 solos cuando ocurre un evento y se borran escribiendo un 1; escribir ciertos bits dispara una acción. Por eso hay que leer la hoja de datos de cada registro.
08 · Mapa concreto
El espacio de datos del ATmega328P
El AVR tiene tres espacios de direcciones separados: la memoria de programa (Flash), la EEPROM y el espacio de datos. Este último reúne los registros de la CPU, los de los periféricos y la SRAM:
| Direcciones | Contenido | Cantidad |
|---|---|---|
0x0000 – 0x001F | Registros de trabajo R0 a R31 | 32 bytes |
0x0020 – 0x005F | Registros de E/S (puertos, SREG, SP, temporizador 0…) | 64 bytes |
0x0060 – 0x00FF | Registros de E/S extendidos (ADC, UART, I2C, temporizadores 1 y 2…) | 160 bytes |
0x0100 – 0x08FF | SRAM interna | 2048 bytes |
Algunos registros que vas a usar muy pronto:
| Registro | Dirección de datos | Dirección de E/S | Función |
|---|---|---|---|
| PINB | 0x23 | 0x03 | Lee el nivel de los pines del puerto B |
| DDRB | 0x24 | 0x04 | Elige entrada o salida de cada pin |
| PORTB | 0x25 | 0x05 | Escribe el nivel de salida del puerto B |
| SREG | 0x5F | 0x3F | Banderas de estado de la CPU |
| ADMUX | 0x7C | — | Configura el conversor analógico |
| UDR0 | 0xC6 | — | Dato de la UART |
La doble numeración suele confundir: en el espacio de E/S, PORTB es la posición 0x05, la que usan las instrucciones IN y OUT; en el espacio de datos, la misma posición es 0x25, que es la que usa el C al trabajar como memoria. Son el mismo registro.
09 · Analogía con programación
Un bus que despacha por dirección
Podés modelar el espacio de datos como una función que recibe una dirección y decide qué bloque responde. En el código siguiente, escribir en 0x25 «mueve los pines» y escribir en 0x0100 guarda un byte en la SRAM.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
class Bus:
"""Modelo mínimo del espacio de datos de un ATmega328P."""
def __init__(self):
self.sram = bytearray(0x800) # 0x0100 a 0x08FF
self.portb = 0
def escribir(self, direccion, valor):
if direccion == 0x25: # PORTB
self.portb = valor
print(f" pines PB7..PB0 = {valor:08b}")
elif 0x0100 <= direccion <= 0x08FF:
self.sram[direccion - 0x0100] = valor
else:
raise ValueError(f"ningún bloque responde en {direccion:#06x}")
def leer(self, direccion):
if direccion == 0x25:
return self.portb
if 0x0100 <= direccion <= 0x08FF:
return self.sram[direccion - 0x0100]
raise ValueError(f"ningún bloque responde en {direccion:#06x}")
bus = Bus()
bus.escribir(0x0100, 42) # una variable en SRAM
print("SRAM[0x0100] =", bus.leer(0x0100))
print("Escribo en PORTB:")
bus.escribir(0x25, 0b00100000) # PB5, el LED de la placa Uno
print("Leo PORTB =", f"{bus.leer(0x25):08b}")
try:
bus.escribir(0x0900, 1) # fuera de la SRAM
except ValueError as error:
print("Error:", error)
Resultado: 42 en la SRAM, los pines cambian a 00100000 y la dirección 0x0900 produce un error. Probá escribir en 0x24 o en 0x0800 y predecí qué ocurre antes de ejecutar.
El modelo sirve para ver la lógica del decodificador de direcciones. Tiene un límite: en el chip real escribir una dirección inexistente no lanza una excepción; simplemente nadie responde, y el error pasa inadvertido hasta que algo falla.
10 · Exploración interactiva
Direcciones y buses
La primera herramienta indica a qué bloque del ATmega328P pertenece una dirección del espacio de datos. La segunda compara cuántos ciclos necesita una CPU con un solo bus frente a una con dos buses, con un modelo simplificado.
Explorador de la arquitectura
1. ¿De quién es esta dirección?
2. Un bus frente a dos buses
El modelo de la segunda herramienta supone que cada instrucción necesita un acceso para ser buscada y que las que usan datos necesitan uno más. Con un solo bus, los accesos se suman; con dos buses, el acceso a datos se superpone con la búsqueda siguiente. Ignora los saltos, las instrucciones multiciclo y las memorias lentas: sirve para ver la tendencia, no para predecir tiempos exactos. Los tiempos usan un reloj de 16 MHz (62,5 ns por ciclo).
Recorrido guiado
- Probá con
0x25: es PORTB, registro de E/S en la posición 5. Luego probá0x05: es el registro R5 de la CPU, no PORTB. La misma cifra significa cosas distintas en espacios diferentes. - Escribí
0x08FF: último byte de la SRAM. Después0x0900: ningún bloque responde. - Probá
0xC6(UDR0) y0x7C(ADMUX): están en la zona de E/S extendida. - En la segunda herramienta, dejá 200 instrucciones y mové el porcentaje de datos de 0 a 100 %. Con 0 % no hay diferencia; con 100 %, el bus único tarda el doble.
- Agrandá el número de instrucciones: la relación no cambia, porque el modelo es proporcional.
Desafío: la dirección que parece PORTB pero no lo es
Escribí 5. El explorador responde R5, un registro de la CPU. Si en un programa intentaras escribir a la dirección de datos 0x05 para encender un LED, estarías modificando un registro de trabajo y probablemente corromperías el programa. Es la razón de usar siempre los nombres definidos por el fabricante.
11 · Método de trabajo
Cómo leer un diagrama de bloques
- Ubicá la CPU y los buses. Identificá si hay uno o varios y si las memorias están separadas.
- Listá las memorias con su tamaño y qué guarda cada una.
- Buscá los periféricos que necesita tu proyecto y comprobá que existan en cantidad suficiente.
- Seguí el camino de un pin. Un pin suele compartirse entre varias funciones: puerto digital, entrada analógica, línea de un bus serie. Anotá cuál usarás.
- Consultá el mapa de memoria para conocer las direcciones de los registros.
- Verificá el reloj y la alimentación de cada bloque: algunos periféricos pueden desactivarse para ahorrar energía.
Con el diagrama de bloques en una mano y la tabla de registros en la otra se puede planificar un diseño antes de escribir una línea de código. En la mesa de trabajo, el multímetro confirma la alimentación del chip y el osciloscopio permite ver que un registro configurado produce la señal esperada en el pin.
12 · Comprensión
Confusiones frecuentes
«En una arquitectura Harvard no se pueden leer datos del programa»
Sí se puede, pero mediante instrucciones o macros especiales, porque los espacios de direcciones son distintos. En un AVR se usa LPM o pgm_read_byte.
«Los registros de periféricos son memoria como cualquier otra»
Ocupan direcciones, pero cada bit tiene un efecto propio. Leer puede devolver un estado externo, escribir puede disparar una acción y algunos bits se borran de forma particular. Se trabaja con la hoja de datos delante.
«Más líneas de dirección significa más memoria disponible»
Amplía lo que se podría direccionar, no lo que existe. La memoria física real la fija el fabricante; el resto del espacio queda vacío.
«La CPU ejecuta mientras el periférico espera»
Los periféricos funcionan por su cuenta una vez configurados. Un temporizador sigue contando y un conversor sigue midiendo aunque la CPU esté haciendo otra cosa o dormida.
«El mapa de memoria es igual en todos los microcontroladores»
Cambia entre familias e incluso entre modelos de una misma familia. Las direcciones de este tema valen para el ATmega328P; para otro chip hay que consultar su hoja de datos.
13 · Práctica guiada
Predecí antes de comprobar
1. Contar direcciones
Una memoria tiene 12 líneas de dirección y un byte por posición. ¿Cuántos bytes puede direccionar? ¿Y con 16 líneas?
Ver solución
Con 12 líneas: 2¹² = 4096 bytes (4 KB). Con 16 líneas: 2¹⁶ = 65 536 bytes (64 KB). Cada línea adicional duplica el espacio.
2. Traducir direcciones
El registro PORTD tiene dirección de E/S 0x0B. ¿Cuál es su dirección en el espacio de datos y qué región es?
Ver solución
Se suma 0x20: 0x0B + 0x20 = 0x2B. Está en la región de registros de E/S (0x20 a 0x5F). Podés comprobarlo en el explorador.
3. Un bus o dos
Un programa ejecuta 200 instrucciones y el 25 % accede a datos. Con el modelo del tema y un reloj de 16 MHz, ¿cuánto tarda con un solo bus y con dos?
Ver solución
Accesos a datos: 50. Con un bus: 200 + 50 = 250 ciclos = 15,6 µs. Con dos buses: 200 ciclos = 12,5 µs. El bus único es 1,25 veces más lento.
4. Una dirección sin dueño
Un programa escribe un byte en la dirección 0x0A00 de un ATmega328P. ¿Qué ocurre?
Ver solución razonada
La dirección supera el final de la SRAM (0x08FF), de modo que ningún bloque la implementa. El valor se pierde y una lectura posterior no devuelve lo escrito. Es un error silencioso, típico de desbordes de arreglos o de punteros mal calculados.
5. Constantes en un AVR
Un programa define una tabla de 600 bytes de solo lectura. ¿Por qué conviene guardarla en Flash y no en SRAM en un ATmega328P?
Ver solución razonada
Ocuparía casi un tercio de los 2048 bytes de SRAM, que se necesitan para variables y pila. En Flash, que tiene 32 KB y no se pierde al apagar, no compite con ellas. Hay que declararla con PROGMEM y leerla con pgm_read_byte, porque la Flash es otro espacio de direcciones.
14 · Síntesis
Ideas para recordar
- Un microcontrolador se compone de CPU, memorias, periféricos y buses.
- Con n líneas de dirección se distinguen 2ⁿ posiciones; el espacio direccionable es un máximo, no la memoria real.
- En von Neumann hay un solo camino para programa y datos; en Harvard hay caminos separados, y el AVR es de este tipo.
- La CPU contiene un contador de programa, registros de trabajo, una ALU, banderas de estado y una unidad de control.
- Flash guarda el programa, SRAM las variables y EEPROM los datos persistentes.
- Los periféricos se manejan mediante registros mapeados en memoria: escribir una dirección actúa sobre hardware.
- El espacio de datos del ATmega328P va de los registros de CPU (0x0000) a la SRAM (hasta 0x08FF), con los registros de E/S en el medio.
¿Listo para el tema 4?
Antes de avanzar, explicá la diferencia entre Harvard y von Neumann con un ejemplo, calculá cuántas direcciones hay con 14 líneas y ubicá PORTB en los dos espacios de direcciones. Si podés justificar las tres respuestas, ya podés leer el diagrama de bloques de cualquier microcontrolador.
En el próximo tema pasamos a la mesa de trabajo con el taller de anatomía del ATmega328P y la placa: pines, alimentación, reset, reloj y conexión a la computadora.