Arduino, ATMega328 manipulación de puertos - mapeo pin y puertos

Actualizado: 16 abril 2022

La manipulación de puertos permite leer, escribir y asignar la función de lectura/escritura de los pines de Arduino (o ATMega328) 60 veces más rápido y utilizando menos memoria.

En la mayoría de casos la manipulación de puertos sería exagerado (sobre todo para proyectos simples - como por ejemplo el proyecto efecto llamas); pero a veces, como veremos, es muy útil para la optimicación y en unos casos ¡imprescindible!

A continuación encontrarás tablas que muestra el mapeo entre los pines del Arduino o AtMega328 y su operación homóloga estilo manipulación de puertos. Puedes seleccionar combinaciones de pines multiples en la primera tabla y ver el resultado de una sol linea de código en la segunda tabla.

Entonces si ya dominas el concepto de manipulacion de puertos y solo necesitas una chuleta, puedes pasar de la explicación y saltar directamente a las tablas de mapeo; y luego crear una marcapáginas en tu navegador para tener siempre acceso conveniente cuando estés desarollando.

Cómo funciona la manipulación de puertos

Los pines del AtMega328 (es decir el chip embebido en el Arduino) están agrupados en bloques físicos (los dichosos "puertos")

D, B y C.

Cada puerto contiene hasta 8 pines.

Distribución de puertos Arduino para manipulación de puertos
Distribución de puertos Arduino
Distribución de puertos AtMega328p para manipulación de puertos
Distribución de puertos AtMega328p

Cuando parece que tu código está comunicando con cualquier pin individual, en realidad está comunicando con !todos los pines en el puerto entero mediante un byte de datos!

1 byte son 8 bits, por lo tanto suficiente información para hablar con 8 bits. Los puertos D y B contienen 8 pines; mientras el puerto C solo contiene 7 pines.

Cuando, por ejemplo, escribes

digitalWrite(9, HIGH);

parece que estás diciendo simplemente

"pin 9 - ir HIGH" (es decir: pasar corriente por el pin 9, para encender un LED por ejemplo)

Y que estás haciendo esto por supuesto; ¡pero no estás solo mandando el pin 9 a HIGH! Por la forma de funcionar físicamente el chip AtMega328, en realidad estás hablando con todos los pines del puerto B.

El código subyacente está haciendo esto:

"pin 8 - no hagas nada"
"pin 9 - cambia a HIGH"
"pin 10 - no hagas nada"
"pin 12 - no hagas nada"
"pin 11 - no hagas nada"
"pin 13 - no hagas nada"
"XTAL1 pin - no hagas nada"
"XTAL2 pin - no hagas nada"

Y además, la función digitalWrite() necesita ejecutar más pasos en el fondo.

PERO... es posible combinar multiples comandos en una sola instrucción y saltar los pasos adicionales - es decir usar "manipulación de puertos".

Pero ¿por qué molestarnos en hacer esto cuando ya existen funciones convenientes y perfectamente intuitivas como digitalWrite(), digitalRead() y pinMode()?

Dos razones:

  1. para ahorrar espacio de memoria
  2. rapidez

Ambas ventajas incrementan cuando hablas con multiples pines a la misma vez. La manipulación de puertos te permite interactuar con todos los pines de un puerto con una sola instrucción.

Por ejemplo, en vez de escribir (para Arduino)

    digitalWrite(2, HIGH);
    digitalWrite(4, HIGH);
    digitalWrite(6, HIGH);
    digitalWrite(7, HIGH);  
...puedes escibir simplemente:
    PORTD |= 212;  

¿¿¿Y eso...???

Sintaxis de manipulación de puertos

Antes de meternos en la manipulación simultanea, miramos un ejemplo más básico.

  PORTB |= 4;  
es igual que
  digitalWrite(10, HIGH);

A primera vista PORTB |= 4; puede parecer un poco raro (sobre todo si no conoces las operaciones bit a bit) pero en realidad es código C muy normal.

Se trata simplemente de una variable (PORTB), seguida por un operador (|=), seguido por un valor (4).

Entramos en detalle:

La variable

En este caso es una variable especial del sistema usada para la manipulación de puertos. Representa un "registro" (una zona de memoria usada por el microcontrolador para gestionar su estado). Variables de registro están nombradas conforme al siguiente patrón de prefijos y sufijos:

la instrucción seguida por la letra del puerto

La siguiente tabla muestra las 3 instrucciones con su significado y la versión equivalente de alto nivel (se sustitutiría la x por el puerto que te interese):

Instrucción estilo manipulación de puertos Significado Equivalente estilo alto nivel
DDRx informar la dirección de datos de uno o más pines como INPUT u OUTPUT (es decir entrada o salida) pinMode()
PINx leer el estado de uno o más pines digitalRead()
PORTx informar el estado de uno más pines (HIGH o LOW) digitalWrite()

En nuestro ejemplo básico PORT es un prefijo que significa digitalWrite() y el sufijo B significa el puerto B.

Por lo tanto PORTB... dice "escribamos (HIGH o LOW) a uno o más pines del puerto B".

El operador

El operador es un operador tipo bit a bit (bitwise en inglés) porque estamos manipulando bits individuales.

Solo tienes que conocer tres de estos operadores para manipular puertos pero sus efectos son muy diferentes en función de cómo los combines con los posibles valores.

  • |= asignación bit a bit OR
  • &= asignación bit a bit AND
  • & condición bit a bit AND

Vamos a ver cómo se usan. Todos los ejemplos a continuación actuan sobre el pin 10 del Arduino (recuerda que el 4 en cada ejemplo es binario 100 y que el pin 10 del Arduino es puerto B - bit "2", que es la tercera posición desde la derecha):

Función equivalente de alto nivel Sintaxis Ejemplo Explicación
pinMode(pin, OUTPUT) DDRx |= valor DDRB |= 4 Este ejemplo asigna pin 10 del Arduino como salida.
pinMode(pin, INPUT) DDRx &= valor invertido DDRB &= ~4 ¡El signo ~ es importante! Convierte los todos los bits que hayamos especificado a zero sin afectar a los demás bits.
Este ejemplo asigna pin 10 del Arduino como entrada.
if (digitalRead(pin) == HIGH) .... (PINx & value) if (PINB & 4) .... Este ejemplo comprueba si pin 10 del Arduino está HIGH.
if (digitalRead(pin) == LOW) .... (!PINx & value) if (!PINB & 4) .... Este ejemplo comprueba si pin 10 del Arduino está LOW.
digitalWrite(pin, HIGH) PORTx |= value PORTB |= 4 Este ejemplo manda pin 10 del Arduino a HIGH.
digitalWrite(pin, LOW) PORTx &= flipped value PORTB &= ~4 ¡El signo ~ es importante! Convierte los todos los bits que hayamos especificado a zero sin afectar a los demás bits.
Este ejemplo manda pin 10 del Arduino a LOW.

El valor

El valor es un numérico que indica con qué pin o con qué conjunto de pines estamos hablando.

Cada puerto está representado por un byte (8 bits); y cada pin de un puerto dado está representado por un bit. Por lo tanto disponemos de hasta 8 pins para cada operación de manipulación de puertos. En el caso del puerto B hay 8 pines que corresponden a los siguientes huecos:

Pin de Arduino Posición en el puerto B
8 0
9 1
10 2
11 3
12 4
13 5
13 5
XTAL1 6
XTAL2 7

Al utilizar la variable PORTx, si declaras un bit como 1, su pin asociado irá a HIGH; si declaras un bit como 0, su pin asociado irá LOW. De hecho las variables HIGH y LOW son simplemente constantes que representan 1 y 0. digitalWrite(10, HIGH), por ejemplo, en realidad significa digitalWrite(10, 1).

|= es el operador de asignación OR bit a bit de C++. Es un poco como = pero en vez de informar el valor de la variable entera, modifica bits individuales de la variable a 1. Y como cada bit representa un pin |= manda nuestros pines seleccionados a HIGH.

¿¿¿Pero con qué pines estamos hablando???

En este ejemplo solo apuntamos a un solo pin, ¿pero cuál? La tabla anterior muestra que el pin 10 del Arduino es puerto B posición 3. Entonces ¿que significa el 4 en el código PORTB |= 4?

4 es 100 en binario; por lo tanto estamos interactuando con la 3ª bit desde la derecha, que corresponde al pin 10 del Arduino.

Arduino pin XTAL2 XTAL1 13 12 11 10 9 8
Port B position 7 6 5 4 3 2 1 0
value 128 64 32 16 8 4 2 1

Ahora volvemos a nuestro primer ejemplo, que manipula varios pines simultaneamente: PORTD |= 212;.

El valor 212 representa aquellos bits del puerto PORTD que queremos establecer como 1.

En binario 212 se escribe: 11010100. Los dígitos que se ven como 1 son los bits afectados. Por lo tanto, si contamos de derecha a izquierda, y teniendo en cuenta que la primera posición se anomena 0, estamos manipulando pines 2, 4, 6 y 7.

Ten en cuenta que en el puerto D hay 8 pines y de forma serendípita la numeración de los pines de Arduino corresponden a las posiciones de este puerto (la numeración de los pines del AtMega3228 -que no está alienada con la numeración del Arduino, no coincide de la misma forma).

Pin de Arduino 7 6 5 4 3 2 1 0
Posición de bit del puerto D 7 6 5 4 3 2 1 0
Valor 128 64 32 16 8 4 2 1

De esta forma

    PORTD |= 212;  
equivale (en Arduino) a:
    digitalWrite(2, HIGH);
    digitalWrite(4, HIGH);
    digitalWrite(6, HIGH);
    digitalWrite(7, HIGH);  
...pero 60 veces más rápido :)

Todos los demás bits (en este caso 0, 1, 3 y 5) no están afectados.

La siguiente tabla muestra el mapeo los pines del AtMega328, el Arduino UNO, puertos y bits de registro para la manipulación de puertos. Para cada pin se ve la versión de código de manipulación de puertos para las operaciones pinMode(), digitalRead() y digitalWrite(). Puedes utilizar las opciones Mostrar operaciones como: para mostrar los valores en base decimal, hexadecimal o binario. Ten en cuenta que cada formato es simplemente una forma diferente de escribir el mismo número. A veces creo que binario es la base más intuitiva ya que un número binario (por definición) es una representación visual de los bits que estés manipulando.

Para generar el código para manipular varios pines simultaneamente haz marca las casillas en la columna combinar para cada pin que te interese. El comando estilo manipulación de puertos aparecerá automágicamente en la tabla Operaciones simultaneas en pines multiples por debajo :)

Tabla de mapeo de pines a puerto ATMega328/Arduino

Mostrar operaciones como:

Manipulación de puertos

Atmega
328
Arduino
UNO
pin también
sirve
puerto bit de
registro
pinMode
(OUTPUT)
pinMode
(INPUT)
digitalRead(x, HIGH) digitalRead(x, LOW) digitalWrite(HIGH) digitalWrite(LOW) combinar

Operaciones simultaneas en pines multiples

port pinMode(OUTPUT) pinMode(INPUT) digitalRead(x, HIGH) digitalRead(x, LOW) digitalWrite(HIGH) digitalWrite(LOW)

Aclaraciones sobre las tablas

  • Cómo mostrar valores en diferentes bases

    Usa los botones Mostrar operacions como: para mostrar los valores en formato decimal, hexadecimal o binario. Ten en cuenta que esto no cambia los valores de ninguna manera; simplemente los formatea en una base diferente. Por ejemplo, 16, 0x10 y B10000 son formas diferentes de representar el mismo número.

  • Combinar operaciones

    Puedes seleccionar operaciones en pines multiples simultaneamente marcando las casillas en la columna combinar. La operación resultante se muestra para cada puerto en la tabla Operaciones simultaneas en pines multiples.

  • Establecer resistencias pull-up con manipulación de puertos

    Teniendo en cuenta que pinMode(x, INPUT_PULLUP) hace pinMode(x, INPUT) seguido por digitalWrite(x, HIGH), los dos siguientes ejemplos de código consiguen el mismo resultado:

    pinMode(4, INPUT_PULLUP);
    
    DDRD &= ~0x10;   // declarar pin 4 como modo entrada
    PORTD |= 0x10;   // mandar pin 4 a HIGH
    
  • Numeración de pines AtMega328

    La numeración de pines del AtMega328 corresponde al formato 28-PDIP.

  • ¿Qué pines aparecen en la tabla?

    La tabla muestra todos los pines que estén disponibles para la maniulación de puertos.

    Por lo que sepa, (según de mis pruebas y lo que he leido en ATMEGA 328/P datasheet), estos son todos los pines que se pueden aprovechar para lectura/escritura digital; y todos brindan una resistencia tipo pull-up interna.

  • ¿Por qué el pin RESET está en gris?

    No es imposible usar el pin RESET para operaciones IO digital (y por lo tanto la manipulación de puertos) pero lo más probable es que no querrás. El pin RESET es una entrada que siempre está HIGH durante tiempo de ejecución normal (o sea hay corriente entrando mediante su resistencia pull-up interna). En el caso de ir a LOW, cualquier proceso en marcha en ese momento se termina y el programa se reanuda. Este pin está pensado para ser apagado mediante un botón físico. También esto se puede lograr de forma programática, claro; pero en cuánto a la manipulación de puertos no nos va a servir casi nada.

  • ¿Por qué los pines XTAL están en gris?

    Los pines XTAL ("cristal") sirven para conectar a un oscilador de cristal, es decir un reloj que garantiza un ritmo constante del funcionamiento del chip.

    Desde la placa del Arduino no están disponibles ya que los pines XTAL ni siquiera existen como conectores hembras. Están físicamente soldados a la placa y están conectados al reloj de cristal de 16MHz a través de dos condensadores (claramente visibles en la placa).

    Oscilador de cristal y condensadores del Arduino UNO
    Oscilador de cristal y condensadores del Arduino UNO

    Ambos pines XTAL están accesibles en el chip ATMega en modo stand-alone Funcionando de forma independiente, sin estar embebido en la placa del Arduino pero probablemente no te serán muy útiles.

    En el AtMega328 los pines "XTAL" son simplemente dos pines más (9 y 10). Pero esto supone que estés utilizando el reloj interno del ATMega de 8MHz. Esto se hace cargando un bootloader El cargador de arranque - es decir el código que configura el chip antes de que tu programa empiece a ejecutar. especial. Hay varias formas de conseguir esto - incluso desde el IDE de Arduino.

    Ventajas de prescindir del reloj externo en un proyecto stand-alone
    • ahorro de energía (importante si tienes pensado montar tu chisme en medio de un inmenso olivar)
    • ahorro de cableado/soldadura (importante si ...bueno, creo que ni hace falta explicar por qué esto es importante)
    Desventajas de prescindir del reloj externo en un proyecto stand-alone
    • menos velocidad (el reloj interno del AtMega328 corre a 8MHz comparado a los 16MHz del reloj externo que viene incluido con el Arduino)
    • menos precisión (el reloj interno del ATMega es menos constante - cosa que puede ser perfectamente admisible para muchos proyectos; pero completamente inservible si tu lógica depende de una precisión de microsegundos)
  • Columna pin tambień sirve

    Funciones especiales, tal como la comunicación con otros chips o dispositivos, se realizan mediante pines concretos. Si tu programa usa unas de estas funciones, tendrás que evitar depender de sendos pines para escritura/lectura digital para permitir el funcionamiento corecto del chip. La columna pin tambień sirve muestra las funciones alternativas más típicas.

    • TX, RX

      Evita estos pines si tienes pensado usar la comunicación serial USART. Ten en cuenta que la ¡comunicación serial incluye la conexión USB! O sea, si tu código incluye Serial.println(···) para escribir a la consola, tendrás que evitar estos dos pines. Incluso si tu programa final no depende de comunicación serial, tus mensajes de depuración afectarán al comportamiento de los pines TX y RX.

    • MOSI, MISO, SCK

      Evita esto pines si necesitas comunicaciones tipo SPI. El pin 16 del ATMega328 también lleva el señal habilitar/deshabilitar de esclavo (SS); Por lo tanto no podrás utilizar ese pin si tu código SPI necesita esta funcionalidad.

    • SCL, SDA

      Evita estos pines si vas a usar la comunicación tipo I2C.

© 2022