01 · Punto de partida
Qué vamos a construir
Un teclado numérico de 16 teclas conectado al microcontrolador, que muestra el código de la tecla apretada en cuatro LED en binario. Es un proyecto pequeño, pero ejercita lo que aprendiste en el bloque: configurar pines como entradas con pull-up y como salidas, escribir un puerto entero, manejar la alta impedancia, leer niveles, detectar flancos y filtrar el rebote.
Los materiales son un teclado matricial de membrana de 4×4 (con un conector de 8 pines), cuatro LED con resistencias de 330 Ω, la placa Uno, una protoboard y el multímetro. Opcionalmente, diodos 1N4148 si querés estudiar las teclas fantasma.
Retomamos los temas 15 a 17: salidas, estados de los pines, pull-up, alta impedancia y antirrebote por muestreo.
02 · El problema de los pines
La idea de la matriz
Conectar cada tecla a su propio pin cuesta un pin por tecla: 16 teclas, 16 pines. Un microcontrolador pequeño se queda sin pines enseguida. La solución es ordenar las teclas en una matriz de filas y columnas, con una tecla en cada cruce. Una tecla cerrada conecta su fila con su columna.
El algoritmo, llamado escaneo o multiplexado: se baja una fila a la vez, y se lee qué columnas se vuelven 0. Una columna en 0, con una fila en 0, significa que la tecla de ese cruce está cerrada. Con f filas y c columnas se manejan f · c teclas con solo f + c pines: 16 teclas con 8 pines; 64 teclas con 16.
Es el mismo principio de la memoria del curso anterior: un decodificador de filas y una lectura de columnas. Aquí lo hace el programa. Lo vas a encontrar también en displays de siete segmentos y en matrices de LED, que se multiplexan para ahorrar pines.
03 · Paso 1
El cableado
| Función | Pin del chip | Pin de la placa | Configuración |
|---|---|---|---|
| Filas F0 a F3 del teclado | PD4 a PD7 | D4 a D7 | Salida en bajo cuando están activas; entrada Hi-Z cuando no |
| Columnas C0 a C3 del teclado | PB0 a PB3 | D8 a D11 | Entradas con pull-up interno |
| LED del código (bit 0 a 3) | PC0 a PC3 | A0 a A3 | Salidas, cada una con 330 Ω a masa |
Los pines de un mismo puerto se agrupan en un nibble: las filas en la mitad alta de PORTD y las columnas en la mitad baja de PORTB. Así se manejan con máscaras de 4 bits, en el estilo del tema 10.
Los teclados de membrana tienen un conector plano de 8 contactos, pero el orden de las filas y las columnas varía según el modelo. Antes de cablear, identificalas con el multímetro en continuidad: apretá una tecla y buscá entre qué dos contactos suena. Las teclas de una misma fila comparten un contacto, y las de una misma columna comparten el otro.
04 · Paso 2
El escaneo
La configuración de los pines es el truco: las filas se manejan como un colector abierto emulado (tema 16). Cada fila tiene su bit de PORTD en 0 de forma permanente, y se activa poniendo su bit de DDRD en 1 (la fila baja a 0). Cuando DDRD vale 0, la fila queda en alta impedancia.
void teclado_init(void) {
PORTD &= 0x0F; // filas: PORT en 0 (si DDR=1, sale 0; con DDR=0 es Hi-Z)
DDRD &= 0x0F; // todas las filas inactivas (alta impedancia)
DDRB &= ~0x0F; // columnas: entradas
PORTB |= 0x0F; // con pull-up
}
// Devuelve un bit por tecla: bit (4·fila + columna) en 1 si está cerrada
uint16_t escanear(void) {
uint16_t cerradas = 0;
for (uint8_t fila = 0; fila < 4; fila++) {
DDRD = (DDRD & 0x0F) | (0x10 << fila); // bajar solo esta fila
_delay_us(10); // dejar que las columnas se asienten
uint8_t columnas = (~PINB) & 0x0F; // 1 = columna en 0 = tecla cerrada
cerradas |= (uint16_t)columnas << (4 * fila);
}
DDRD &= 0x0F; // soltar las filas
return cerradas;
}
Por qué la pausa de 10 µs: cuando una fila deja de estar activa, la columna que estaba en 0 mediante una tecla queda en alta impedancia y tiene que volver a subir por el pull-up de 20 a 50 kΩ, cargando la capacidad del cable y del teclado (algunas decenas de picofaradios). La constante de tiempo es del orden de 1 a 5 µs. Si se lee antes, la columna aún está baja y aparece una tecla que no existe en la fila nueva.
Con (~PINB) & 0x0F se invierte la lógica activa en bajo: ahora un 1 significa «tecla cerrada». Y devolver las 16 teclas en un entero de 16 bits tiene una ventaja: el estado completo del teclado cabe en una variable, que se compara, se filtra y se recorre con operaciones de bits.
05 · Paso 3
Teclas fantasma y diodos
Con tres teclas apretadas en los vértices de un rectángulo (por ejemplo F0·C0, F0·C1 y F1·C0), el escaneo detecta una cuarta tecla que nadie apretó (F1·C1). Al bajar la fila F1, la columna C0 baja por la tecla F1·C0; esa columna está unida a F0 por la tecla F0·C0, y F0 a C1 por F0·C1, de modo que C1 también baja y parece que F1·C1 está cerrada. La corriente encuentra un camino alternativo por tres teclas.
El problema es de la matriz, no del programa. Tres soluciones:
- Un diodo en serie con cada tecla (cátodo del lado de la fila). La corriente solo puede ir de la columna a la fila activa, y los caminos alternativos quedan bloqueados. Cuesta un diodo por tecla y permite apretar cualquier combinación.
- Aceptar la limitación: detectar la situación (tres teclas en un rectángulo) e ignorar la combinación. Es lo que hacen los teclados económicos y por eso, en un teclado de computadora sin diodos, a veces tres teclas a la vez no responden.
- Limitar el uso a una tecla a la vez, como en un teclado numérico de un cajero.
06 · Paso 4
Antirrebote de la matriz
Cada tecla rebota (tema 17). Como el escaneo devuelve el estado de las 16 teclas en un entero, el filtro es sencillo: se acepta un estado nuevo solo cuando se repite en varios escaneos consecutivos. Es el método de «N lecturas iguales», aplicado a los 16 bits a la vez.
static uint16_t candidato = 0, estable = 0;
static uint8_t cuenta = 0;
// Se llama una vez cada 5 ms. Devuelve las teclas recién presionadas.
uint16_t teclas_nuevas(void) {
uint16_t ahora = escanear();
uint16_t nuevas = 0;
if (ahora != candidato) { // cambió algo: empezar a contar de nuevo
candidato = ahora;
cuenta = 0;
} else if (cuenta < 3 && ++cuenta == 3) { // tres escaneos iguales (15 ms): estable
nuevas = candidato & ~estable; // bits que ahora valen 1 y antes valían 0
estable = candidato;
}
return nuevas;
}
La línea candidato & ~estable es la detección de flancos del tema 17 aplicada a los 16 bits: devuelve solo las teclas que acaban de cerrarse, una vez por pulsación. Con un período de 5 ms y tres escaneos, la latencia es de 15 ms. El filtro cubre un rebote de hasta unos 10 ms.
07 · Paso 5
El programa completo
Con las partes anteriores, el programa principal muestra el índice de la primera tecla nueva en los cuatro LED:
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
static const char TECLAS[16] = {'1','2','3','A', '4','5','6','B', '7','8','9','C', '*','0','#','D'};
static uint8_t indice(uint16_t mascara) { // posición del bit menos significativo
uint8_t i = 0;
while (!(mascara & 1)) { mascara >>= 1; i++; }
return i;
}
int main(void) {
teclado_init();
PORTC &= ~0x0F; // LED apagados antes de volverlos salidas
DDRC |= 0x0F;
while (1) {
uint16_t nuevas = teclas_nuevas();
if (nuevas) {
uint8_t k = indice(nuevas); // tecla 0 a 15
PORTC = (PORTC & ~0x0F) | k; // mostrar el código en binario
/* TECLAS[k] es el carácter: lo usaremos con la UART */
}
_delay_ms(5); // base de tiempo simple
}
}
Dos notas de diseño. El _delay_ms(5) es una base de tiempo simple y bloqueante: sirve en este proyecto porque el programa no hace otra cosa. En un programa real, el escaneo se llamaría desde la marca de 5 ms de un temporizador (tema 11), y el resto del tiempo estaría libre. Y TECLAS[k] traduce el índice al carácter de la tecla; cuando tengamos la UART (tema 32) lo enviaremos a la computadora.
08 · Paso 6
Verificar
- Cada tecla da su código. Apretá las 16 y comprobá que los LED muestran 0 a 15 (la tecla «1» es 0000; la «D», 1111). Si una tecla da el código de otra, hay un cable cambiado.
- Una pulsación, un evento. Mantené una tecla apretada un segundo: el código no debe cambiar ni repetirse. Apretá varias veces rápido: no debe perderse ninguna.
- La forma de la señal. Con el osciloscopio en una columna, mirá lo que ocurre al cambiar de fila: debe verse la subida por el pull-up con una constante de tiempo de microsegundos. Si ves que no llega al nivel alto en 10 µs, alargá la pausa.
- El tiempo del escaneo. Con una marca en un pin (tema 14), medí la duración de
escanear(): debería rondar los 40 a 50 µs con la pausa de 10 µs por fila, es decir, menos del 1 % de un período de 5 ms. - Las teclas fantasma. Apretá tres teclas en un rectángulo: sin diodos, el código de una cuarta puede aparecer. Con diodos, no.
Si algo falla, el orden de sospecha es: la identificación de filas y columnas (continuidad), el pull-up de las columnas, el orden de las instrucciones de teclado_init(), la pausa de asentamiento y, al final, el filtro.
09 · Analogía con programación
Simular la matriz y sus fantasmas
Una matriz de teclas es un grafo: filas y columnas son nodos, y las teclas cerradas son aristas. Una columna lee 0 cuando está en la misma componente conexa que la fila activa. Este programa calcula el escaneo con y sin diodos para cuatro combinaciones de teclas:
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
TECLAS = [["1", "2", "3", "A"],
["4", "5", "6", "B"],
["7", "8", "9", "C"],
["*", "0", "#", "D"]]
def escanear(cerradas, diodos=False):
"""Filas inactivas en alta impedancia. Una columna se lee en 0 si está conectada a la fila activa."""
detectadas = set()
for fila in range(4):
if diodos:
alcanzadas = {c for (f, c) in cerradas if f == fila} # solo el camino directo
else:
filas, columnas, cambio = {fila}, set(), True
while cambio: # propagar por teclas cerradas
cambio = False
for f, c in cerradas:
if f in filas and c not in columnas:
columnas.add(c)
cambio = True
if c in columnas and f not in filas:
filas.add(f)
cambio = True
alcanzadas = columnas
detectadas |= {(fila, c) for c in alcanzadas}
return detectadas
def nombres(teclas):
return sorted(TECLAS[f][c] for f, c in teclas)
casos = {
"dos teclas (1 y 2)": {(0, 0), (0, 1)},
"tres teclas en un rectángulo (1, 2 y 4)": {(0, 0), (0, 1), (1, 0)},
"tres teclas en línea (1, 2 y 3)": {(0, 0), (0, 1), (0, 2)},
"cuatro teclas (1, 3, 7 y 9)": {(0, 0), (0, 2), (2, 0), (2, 2)},
}
for descripcion, cerradas in casos.items():
sin = escanear(cerradas)
con = escanear(cerradas, diodos=True)
print(descripcion)
print(f" sin diodos: {nombres(sin)} fantasmas: {nombres(sin - cerradas)}")
print(f" con diodos: {nombres(con)} fantasmas: {nombres(con - cerradas)}")
Resultado: con dos teclas, tres en línea o cuatro en un rectángulo completo, el escaneo es exacto; con tres teclas en los vértices de un rectángulo (1, 2 y 4) aparece el fantasma «5» sin diodos, y desaparece con diodos. Probá (1,1), (1,3) y (3,1): el fantasma es (3,3), la tecla «D».
El modelo es el caso ideal: no incluye el tiempo de asentamiento ni la resistencia de los pines. En el hardware real, un rectángulo de cuatro teclas completo no tiene fantasma, porque no hay una quinta posición que se confunda.
10 · Exploración interactiva
Teclado virtual
Cerrá teclas con el mouse y mirá lo que detecta el escaneo. El fondo amarillo marca las teclas realmente cerradas; el naranja, las detectadas que nadie apretó. Podés activar los diodos o cambiar la forma de manejar las filas inactivas. La segunda herramienta calcula cuánto cuesta escanear.
Banco del teclado matricial
1. Escaneo de una matriz de 4×4
2. Costo del escaneo y del antirrebote
El simulador modela la matriz como un grafo (el mismo cálculo del bloque de Python). Con los diodos, una columna lee 0 solo si su tecla está cerrada en la fila activa. El costo por fila de la herramienta 2 (unos 30 ciclos más la pausa) es una estimación a verificar con el método del tema 14.
Recorrido guiado
- Cerrá la tecla «5»: el escaneo la detecta y muestra
0x0020. Cerrá también la «9»: son dos teclas y no hay problema. - Soltá todo y cerrá «1», «2» y «4» (tres vértices de un rectángulo): aparece la tecla «5» en naranja. Es el fantasma.
- Activá «Un diodo en serie con cada tecla»: el fantasma desaparece.
- Desactivá los diodos y cambiá las filas inactivas a «Salida en alto»: aparece el aviso de cortocircuito entre salidas. Esto no ocurre con la alta impedancia.
- En la herramienta 2, cambiá a una matriz de 8×8: 64 teclas con 16 pines, y mirá el costo del escaneo. Subí la pausa de asentamiento a 100 µs y observá cómo crece.
Desafío: la combinación que se pierde
Con los diodos desactivados, cerrá las teclas «1», «2», «4» y «5» (los cuatro vértices de un rectángulo completo). El escaneo detecta exactamente esas cuatro, sin fantasmas. Pero si el programa ignorara las combinaciones ambiguas (tres vértices), ¿cómo distinguiría este caso de uno con tres teclas? No puede hacerlo con solo mirar el escaneo, por eso los teclados sin diodos limitan cuántas teclas simultáneas se garantizan.
11 · Método de trabajo
Lo aprendido, para cualquier matriz
- Identificá filas y columnas con continuidad; no te fíes del orden del conector.
- Un lado activo, el otro leído. Una fila baja a 0 (como colector abierto:
PORTen 0,DDRconmuta), las columnas se leen con pull-up. - Inactivo significa alta impedancia, nunca nivel alto.
- Esperá el asentamiento entre cambiar la fila y leer las columnas (unos 10 µs).
- Devolvé el estado completo en una variable y filtralo como un todo (N escaneos iguales); detectá los eventos con
nuevo & ~estable. - Decidí qué hacer con las ambigüedades (diodos, ignorarlas o una tecla a la vez) y verificalo con tres teclas en rectángulo.
El mismo esquema, con las funciones de entrada y salida invertidas, es la forma de manejar una matriz de LED o un display multiplexado: se activa una fila a la vez y se escriben las columnas con el patrón de esa fila, tan rápido que el ojo no ve el parpadeo.
Con este taller termina el bloque 4. Sabés configurar un pin, conocés su electrónica y lees entradas con criterio. El bloque 5 aborda las interrupciones: cómo reaccionar a un evento sin tener que preguntar todo el tiempo.
12 · Comprensión
Confusiones frecuentes
«El teclado de 16 teclas necesita 16 pines»
Con una matriz de 4×4 bastan 8. La contrapartida es que hay que escanear y filtrar por software, y que las teclas simultáneas pueden dar fantasmas.
«Las filas inactivas deben estar en alto para no molestar»
Al contrario: en alto, una tecla cerrada puede unirlas con la fila activa en bajo y provocar un cortocircuito entre salidas. Se dejan en alta impedancia.
«Con leer una vez cada columna alcanza»
Se lee cada columna una vez por fila activa. Sin recorrer las filas no se sabe cuál tecla del cruce está cerrada.
«Los fantasmas se arreglan en el programa»
No: es un problema eléctrico de la matriz. El programa puede detectar situaciones ambiguas y descartarlas, pero la solución real son los diodos.
«Si no hay pausa entre bajar la fila y leer, igual funciona»
Muchas veces sí, y otras no: la columna que acaba de dejar de estar en bajo tiene que subir por el pull-up, que es débil. Un fallo intermitente de este tipo es muy difícil de encontrar, así que conviene asegurar la pausa.
13 · Práctica guiada
Predecí antes de comprobar
1. Contar pines
Querés 25 teclas. ¿Cuántos pines necesita una matriz de 5×5 y cuántas teclas máximas puede manejar una matriz con 8 pines?
Ver solución
Una matriz de 5×5 usa 5 + 5 = 10 pines. Con 8 pines el producto filas × columnas es máximo con 4 y 4: 16 teclas.
2. Del cruce al carácter
La tecla de la fila 2 y la columna 3 tiene el índice 4·2 + 3. ¿Qué índice y qué carácter es en el teclado del taller?
Ver solución
Índice 11. En la tabla TECLAS, la fila 2 es «7 8 9 C»: el carácter es «C». En los LED se vería 1011.
3. Prever un fantasma
Sin diodos, se aprietan las teclas de los cruces (1,1), (1,3) y (3,1). ¿Qué tecla fantasma aparece?
Ver solución
Faltaría el cuarto vértice del rectángulo: el cruce (3,3), que es la tecla «D». La fila 3 baja la columna 1 (por (3,1)), la columna 1 está unida a la fila 1, y la fila 1 a la columna 3, que también baja.
4. Costo del escaneo
Con 4 filas, 30 ciclos por fila más 10 µs de pausa por fila, y 20 ciclos fijos, a 16 MHz: ¿cuánto dura un escaneo y qué parte de un período de 5 ms ocupa?
Ver solución
La pausa de 10 µs son 160 ciclos. Por fila: 30 + 160 = 190. Total: 4 × 190 + 20 = 780 ciclos = 48,75 µs. De 5 ms es el 0,975 %.
5. Latencia de confirmación
Escaneás cada 5 ms y exigís 3 escaneos iguales. ¿Qué latencia tiene la detección y qué rebote máximo cubre?
Ver solución
3 × 5 ms = 15 ms de latencia. Cubre un rebote de hasta unos 10 ms (el filtro necesita que el estado se mantenga durante las tres lecturas, que abarcan 10 ms entre la primera y la última).
14 · Síntesis
Ideas para recordar
- Una matriz de f filas y c columnas maneja f·c teclas con f+c pines; 16 teclas con 8 pines.
- El escaneo baja una fila a la vez y lee las columnas; una columna en 0 indica la tecla cerrada en el cruce.
- Las filas se manejan como colector abierto emulado:
PORTen 0 yDDRconmutando; las inactivas quedan en alta impedancia. - Hace falta una breve pausa de asentamiento entre cambiar la fila y leer las columnas.
- Con tres teclas en los vértices de un rectángulo aparece una cuarta fantasma; los diodos en serie con cada tecla lo evitan.
- El estado de la matriz cabe en un entero de 16 bits que se filtra con N escaneos iguales y se compara con
nuevo & ~estable. - El mismo principio de multiplexado sirve para displays y matrices de LED.
¿Listo para el bloque 5?
Antes de avanzar, explicá cuántos pines ahorra una matriz de 4×4, por qué las filas inactivas van en alta impedancia y cómo aparece una tecla fantasma. Si podés justificar las tres respuestas, cerraste el bloque de entradas y salidas digitales.
En el próximo tema empieza el bloque de interrupciones con vectores y rutinas de servicio: cómo hace el microcontrolador para dejar lo que está haciendo, atender un evento y volver.