Estoy totalmente convencido de que has oído hablar de los pines digitales de Arduino. O por lo menos que has oído hablar de los pines de Arduino. Incluso puede que te hayas atrevido a utilizar las funciones pinMode, digitalWrite y digitalRead.
Quizás sea de las primeras cosas que aprendes con Arduino. Me refiero al típico parpadeo de un LED o blink. Si te fijas en el código dos de estas tres funciones.
Y que voy a poder contar que no se haya dicho ya de los pines de Arduino. Hay mucha información en Internet y fuera de Internet.
Ahora bien, este tutorial no es un tutorial más. En este rincón de Internet dedicado a los pines digitales de Arduino voy a profundizar al más bajo nivel. Nos pondremos el traje de Ant-man para bajar a los registros de memoria.
Pero antes de todo esto hay que aclarar qué son los pines digitales de Arduino y cómo se relacionan con el microcontrolador.
Indice de contenidos
- 1 ¿Qué son los pines digitales de Arduino o GPIO?
- 2 ¿Cuántos pines tiene Arduino?
- 3 Acceso a los pines digitales de Arduino con pinMode, digitalWrite y digitalRead
- 4 Ejemplo de código pines digitales de Arduino
- 5 Cómo de rápido pueden ser los pines digitales de Arduino
- 6 Acceso a los pines digitales de Arduino por registros de memoria
- 7 Acceso a los registros de los pines digitales de Arduino
- 8 Acceso rápido a los pines digitales de Arduino
¿Qué son los pines digitales de Arduino o GPIO?
GPIO significa entradas y salidas de propósito general (en inglés General Purpose Input Output). Puede que a un ingeniero le diga algo pero la verdad, a mi esta descripción no me dice absolutamente nada.
Para mi un pin GPIO, pin digital o pin a secas, es una forma de poder controlar y reaccionar a otros circuitos eléctricos desde un microcontrolador. Gracias a estas entradas y salidas vas a poder leer y enviar información.
Bien, ya tenemos un punto de partida. Son como puertas de comunicación. Un sitio donde por donde vamos a interactuar con otros circuitos. Pero…¿cómo los controlamos un pin?¿cómo funcionan los pines digitales de Arduino?
Al tratarse de circuitos eléctricos lo más lógico es pensar que sea controlando la corriente y la tensión. Y precisamente, esto es lo que hacemos con Arduino, permitir o no permitir circular la corriente. Como si fuera un interruptor.
Por ejemplo, un pin digital de Arduino es útil cuando quieres encender un LED al apretar un pulsador. Un circuito muy sencillo que puedes ver a continuación.
Los pines digitales de Arduino son esos agujeros de plástico que tiene la placa. Ahí es donde se conectan los componentes a través de cables como los que vienen en cualquier kit de Arduino.
Una posible conexión sería conectar el pulsador al pin 6 y el LED al pin 7. Aunque puedes utilizar cualquier pin del 0 al 13 si estás trabajando con Arduino UNO. Otras placas como Arduino MEGA o Arduino Nano tienen otra disposición de pines.
A parte de estos dos componentes necesitamos una resistencia de 10k en configuración pull-down para el pulsador y una resistencia en serie con el LED. Aunque esto no es lo importante en estos momentos.
Lo que importa viene ahora. Si conectas Arduino al puerto USB verás que no sucede nada. Incluso apretando el pulsador. Pero, ¿qué pasa si cargamos este código con el IDE de Arduino?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void setup() { // Iniciar comunicación serie Serial.begin(9600); // Establecer el pin del pulsador en modo entrada pinMode(6, INPUT); // Establecer el pin del LED en modo salida pinMode(7, OUTPUT); } void loop() { // Leer pin digital donde está el pulsador bool estadoPulsador = digitalRead(6); Serial.print("Valor pulsador: "); Serial.println(estadoPulsador); // Comprobar si está pulsado o no if(estadoPulsador == HIGH){ // Encender el LED digitalWrite(7, HIGH); Serial.println("LED encendido"); }else{ // Apagar el LED digitalWrite(7, LOW); Serial.println("LED apagado"); } } |
Que de repente podemos controlar el LED con el pulsador. ¿Magia?, no. Estamos diciendo al hardware que se comporte de una manera determinada utilizando la programación con Arduino.
A ver, en este tutorial no voy a explicar cuál es el camino que hace un código desde que lo programas hasta que se ejecuta en el microcontrolador, pero es necesario entender, a grandes rasgos, qué es lo que sucede dentro de Arduino.
El camino que hace desde el IDE a la memoria se conoce como toolchain o cadena de herramientas (traducción ofrecida por Mario Vaquerizo).
Partimos del código que hemos visto antes que se traduce a código máquina por el compilador y, luego, el AVRDUDE (programita que nos evita una avería mental seria) lo carga a la memoria del microcontrolador.
Lo que realmente estamos cargando a su memoria es algo parecido a esto.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
:100000000C9434000C944F000C944F000C944F004F :100010000C944F000C944F000C944F000C944F0024 :100020000C944F000C944F000C944F000C944F0014 :100030000C944F000C944F000C944F000C944F0004 :100040000C944F000C944F000C944F000C944F00F4 :100050000C944F000C944F000C944F000C944F00E4 :100060000C944F000C944F0011241FBECFEFD4E02E :10007000DEBFCDBF11E0A0E0B1E0E8EFF0E002C0EC :1000800005900D92A030B107D9F711E0A0E0B1E0E2 :1000900001C01D92A030B107E1F70C9467000C94E9 :1000A00000008FEF84B987B98EEF8AB9089501C037 :1000B0000197009759F020E00000000000000000C8 :1000C000000000002F5F2A3599F3F6CF08958FEFD7 :1000D00084B987B98EEF8AB98FEF88B985B98BB9A2 :1000E00084EF91E00E94570018B815B81BB884EF50 :0800F00091E00E945700F0CFDF :00000001FF |
Que para nosotros es Klingon pero para un microcontrolador es su lengua materna.
Básicamente y resumiendo a una expresión mínima este código, se puede decir que lo que hace es acceder a posiciones de memoria. Y cómo diría el expresidente Rajoy: ¿qué es eso?. Tranqui Mariano, luego veremos qué es eso.
Si nos centramos en los pines digitales de Arduino, que es lo que nos interesa, en alguna parte del código en ensamblador estará diciendo a los pines 6 y 7 dos cosas:
- Que funcione como entrada o salida. Como su nombre indica, los GPIO pueden funcionar como entrada o salida.
- Dependiendo de si funciona en modo entrada o salida, tendremos que leer o escribir en el pin.
Más o menos puedes intuir cuales son las sentencias del código anterior que indican estas funciones. Esa es una de las ventajas de los lenguajes de alto nivel como el que utiliza Arduino (C/C++), que es amigable para los humanoides.
Se trata de las funciones pinMode, digitalWrite y digitalRead. Luego veremos más sobre ellas.
Pero antes, vamos a ver cuántos pines digitales tiene Arduino que lo mismo te llevas una chorprecha.
¿Cuántos pines tiene Arduino?
A ver, lo primero tengo que dejar claro que todo ese plástico y pistas que ves en una placa de Arduino está puesto ahí para que podamos utilizar y programar un microcontrolador sin volvernos locos.
Por lo tanto, esos pines donde conectas tus componentes no es más que una extensión de los pines que tiene el microcontrolador.
Lo que pasa es que es mucho más sencillo conectar cables al pin hembra de la placa de Arduino que al propio microcontrolador. Además, algunos pines requieren de electrónica extra para funcionar.
Gracias a esta configuración lo único de lo que te tienes que preocupar es de conectar el cable y listo.
Si nos centramos en Arduino UNO (la placa más popular) el microcontrolador que utiliza es el ATMega328P que tiene esta disposición de pines o pineado.
Y su correspondencia con los pines de Arduino.
Y gracias a que Arduino UNO es hardware libre, esta distribución la puedes ver en el esquemático de la placa. Lo dejo aquí por si tienes curiosidad.
Para que al final se distribuyan los pines digitales de Arduino de esta forma.
Y no solo hay pines digitales de entrada y salida, también hay otro tipo de pines como PWM, analógicos, alimentación, comunicación I2C y SPI. Vamos, el pack completo del microcontrolador.
Por lo tanto, el número de pines que tiene un Arduino dependerá del tipo de microcontrolador que utilice. Si es el ATMega328P como el que lleva Arduino UNO, ya has visto cómo se distribuyen. Si se trata de otra placa tendrás que acudir a la documentación técnica de la misma.
Otra cosa a destacar es el nombre de los pines en el microcontrolador y en la placa. Si te fijas, muchos de ellos no coinciden.
De hecho, en el microcontrolador se pueden distinguir 3 bloques de pines digitales según su nombre.
Están los pines que empiezan por PB, los que empiezan por PC y los que empiezan por PD. Un poco más adelante te diré qué significa esto. De momento con que te quedes que el nombre de los pines en el ATMega328p y Arduino son diferentes es suficiente.
Por último, también es importante destacar algo que no todo el mundo sabe. Los pines analógicos que se numeran del PC0 al PC5 en el ATMega328P y del A0 al A5 en la placa de Arduino, son pines analógicos pero también se pueden utilizar como pines digitales.
Puedes acceder a ellos como si fueran pines digitales de Arduino utilizando la numeración del 14 al 19. Siendo el 14 el pina A0, el 15 el A1 y así sucesivamente hasta el 19.
Entonces, tenemos claro que los pines digitales de Arduino tienen su correspondencia con los pines digitales del microcontrolador. El número de pines depende del microcontrolador que utilice la placa y que los pines analógicos se pueden utilizar como pines digitales.
Y ahora toca ver cómo accedemos y configuramos los pines digitales de Arduino a través del código.
Acceso a los pines digitales de Arduino con pinMode, digitalWrite y digitalRead
No vamos a inventar la rueda. Arduino nos proporciona las funciones necesarias para acceder a los pines digitales de una forma muy sencilla. Esas funciones son pinMode, digitalWrite y digitalRead.
Siempre que te enfrentes a una nueva función lo primero que tienes que hacer es ir a la referencia del lenguaje. En este caso a la referencia de Arduino. Allí vas a encontrar toda la información de cómo usar una función concreta con ejemplos. Eso sí, de momento está en inglés, en alemán y en portugués :)
Veamos que nos dice.
Función pinMode
Normalmente la referencia del lenguaje de Arduino divide la página de ayuda de una función en 4 secciones:
- Description: descripción de lo que hace la función.
- Syntax: cómo se utiliza y se escribe.
- Parameters: los parámetros que tiene la función. Son variables con información que se pasan a la función para que haga algo con ellos.
- Returns: el valor que devuelve la función si es que devuelve alguno.
Una función no es más que un trozo de código (como la función setup o loop) que se ejecuta cuando la llamas o invocas (esto ha sonado a demoni). Puede admitir o no parámetros y puede o no devolver un valor.
Analizando la referencia de pinMode podemos intuir que sirve para configurar si un pin específico se comporta como una entrada (pulsador) o una salida (LED).
La sintaxis es
1 |
pinMode(pin, mode) |
Donde:
- pin: es el número de pin de Arduino donde se quiere establecer el modo.
- mode: el modo que puede ser INPUT, OUTPUT o IMPUT_PULLUP.
Esta función no devuelve ningún valor.
Establecer el modo es lo primero que tienes que hacer con un pin cuando vas a utilizarlo como pin digital de Arduino y normalmente se hace en la función setup.
Solo puedes elegir entre tres modos:
- INPUT: el pin se comporta como entrada digital de Arduino. Para leer su valor o estado se utiliza la función digitalRead.
- OUTPUT: el pin se comporta como salida digital de Arduino. Para establecer su valor o estado se utiliza la función digitalWrite.
- INPUT_PULLUP: el pin se comportan como entrada digital de Arduino pero además, activa una resistencia interna en configuración pull-up. Es como si tuvieras una resistencia en esta configuración conectada al pin.
Por defecto, cualquier pin digital de Arduino está configurado como entrada (INPUT).
Un pin donde hay un pulsador conectado se configura en modo entrada (INPUT) de esta forma:
1 |
pinMode(6, INPUT); |
Y un pin digital de Arduino donde hay un LED conectado se configura en modo salida (OUTPUT) de esta otra forma.
1 |
pinMode(7, OUTPUT); |
Y para establecer o leer un estado en los pines digitales de Arduino se utilizan las funciones digitalWrite y digitalRead.
Función digitalWrite
La referencia del lenguaje nos dice que digitalWrite sirve para establecer un valor de HIGH o LOW en un pin digital de Arduino siempre y cuando se haya configurado como salida (OUTPUT).
El estado HIGH establece un valor de tensión 5V (3,3V en placas que funcionan con 3,3V) y el estado LOW lo establece a 0V.
Además nos dice dos cosas muy interesantes.
La primera es que si un pin está configurado en modo entrada (INPUT) y llamas a la función digitalWrite con el mismo pin, al establecer un estado HIGH estarás activando la resistencia interna pull-up. Y al contrario, con el estado LOW la desactivas. Ahora bien, recomiendan que se utilice el modo explicito INPUT_PULLUP con la función pinMode para activar o desactivar la resistencia pull-up interna.
Por otro lado, si un pin no está configurado en modo salida (OUTPUT) y tienes un LED conectado al mismo pin, al llamar a la función digitalWrite con el estado HIGH el LED se encenderá de forma muy tenue.
Presta atención a esto ya que es un error muy típico. Te olvidas de configurar el modo del pin digital de Arduino y lo utilizas con un LED como salida. Esto provoca una iluminación muy tenue y te vuelves loco buscando errores de conexión.
La sintaxis es
1 |
digitalWrite(pin, value) |
Donde:
- pin: es el pin digital de Arduino al que queremos cambiar el estado.
- value: puede ser o HIGH o LOW, es decir, establecer una tensión en ese pin de 5V (o 3,3V) o 0V.
Como pasa con la función pinMode, digitalWrite tampoco devuelve ningún valor.
El microcontrolador ATMega328p que lleva Arduino UNO puede suministrar un máximo de 40mA por cada pin digital con un máximo total de 200mA. En la práctica se recomienda no superar los 20mA por cada pin. Esto es válido tanto para entradas como para salidas.
Si quieres encender un LED conectado al pin 7 debes utilizar la siguiente sentencia
1 |
digitalWrite(7, HIGH); |
Y para apagarlo utiliza esta otra
1 |
digitalWrite(7, LOW); |
Como ves, muy sencillo todo. Ahora veamos la función digitalRead.
Función digitalRead
La función digitalRead sirve para leer un valor de un pin digital de Arduino. Este valor puede ser o HIGH o LOW.
La sintaxis es
1 |
digitalRead(pin) |
Donde:
- pin: es el pin que se va a leer.
La función digitalRead, al contrario que las funciones pinMode y digitalWrite, devuelve un valor HIGH o LOW. Pero, ¿qué son HIGH y LOW?
HIGH y LOW representan dos estados: alto y bajo, verdadero y falso, encendido o apagado, soltero o casado, … Cómo lo interpretes depende de tu código. En realidad HIGH y LOW son dos constantes que están definidas en el core de Arduino.
Si utilizas el IDE de Arduino 2.0 o PlatformIO, puedes acceder a su valor si te pones encima de HIGH y, con la tecla Ctrl pulsada haces clic sobre la variable.
El archivo que se abre es Arduino.h y en las líneas 40 y 41 puedes ver su valor.
HIHG es 1 y LOW es 0. Esto implica que cuando estableces o lees un estado HIGH con la función digitalWrite o digitalRead, lo que en realidad estás haciendo es pasar un 1 a la función digitalWrite o leyendo un 1 con digitalRead.
Por ese motivo es lo mismo esto
1 |
digitalWrite(7, HIGH); |
Que esto
1 |
digitalWrite(7, 1); |
Y que esto
1 |
digitalWrite(7, true); |
Lo que vengo a decir es que cuando leemos un pin digital de Arduino con la función digitalRead esta nos devolverá un valor 1 (si el estado es HIGH) y 0 (si el estado es LOW).
Y si necesitas almacenar el estado de un pin, el tipo de variable que puedes utilizar es aquel que solo admite dos valores posibles. El tipo de dato bool.
La sintaxis sería la siguiente.
1 |
bool estadoPulsador = digitalRead(6); |
Y después de toda esta teoría veamos un ejemplo donde se accede a los pines digitales de Arduino.
Ejemplo de código pines digitales de Arduino
Con toda esta información ahora es más sencillo entender el código que hemos visto antes donde trabajamos con los pines digitales de Arduino en modo entrada (INPUT) y salida (OUTPUT).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
void setup() { // Iniciar comunicación serie Serial.begin(9600); // Establecer el pin del pulsador en modo entrada pinMode(6, INPUT); // Establecer el pin del LED en modo salida pinMode(7, OUTPUT); } void loop() { // Leer pin digital donde está el pulsador bool estadoPulsador = digitalRead(6); Serial.print("Valor pulsador: "); Serial.println(estadoPulsador); // Comprobar si está pulsado o no if(estadoPulsador == HIGH){ // Encender el LED digitalWrite(7, HIGH); Serial.println("LED encendido"); }else{ // Apagar el LED digitalWrite(7, LOW); Serial.println("LED apagado"); } } |
Comenzamos con la función setup donde iniciamos la comunicación serie y establecemos los modos de los pines digitales. El pin 6 donde está conectado el pulsador en modo entrada (INPUT) y el pin 7 donde está conectado el LED en modo salida (OUTPUT).
1 2 3 4 5 6 7 8 |
void setup() { // Iniciar comunicación serie Serial.begin(9600); // Establecer el pin del pulsador en modo entrada pinMode(6, INPUT); // Establecer el pin del LED en modo salida pinMode(7, OUTPUT); } |
Luego viene la función loop. Lo primero es leer el pin del pulsador, el pin 7, utilizando la función digitalRead. El valor que devuelve se almacena en la variable estadoPulsador de tipo bool.
Puede ser útil mostrar su valor por el monitor serie por eso a continuación, utilizo las funciones Serial.print y Serial.pritln para mostrar el valor de estadoPulsador.
1 2 3 4 5 6 |
void loop() { // Leer pin digital donde está el pulsador bool estadoPulsador = digitalRead(6); Serial.print("Valor pulsador: "); Serial.println(estadoPulsador); |
Cuando alguien active el pulsador su valor será 1 y cuando lo suelte su valor será 0.
Por otro lado vamos a utilizar este valor para encender o apagar el LED. Esto lo hacemos con las sentencias condicionales if-else. Si es 1 (HIGH) enciende el LED y si es 0 (LOW) lo apaga.
1 2 3 4 5 6 7 8 9 10 11 |
// Comprobar si está pulsado o no if(estadoPulsador == HIGH){ // Encender el LED digitalWrite(7, HIGH); Serial.println("LED encendido"); }else{ // Apagar el LED digitalWrite(7, LOW); Serial.println("LED apagado"); } } |
Y con esto y un bizcocho, hasta mañana a las ocho :)
Bueno, quedaría subir el código a la placa y abrir el monitor serie. Verás que pasa muy rápido y prácticamente no puedes ver cuando lo pulsas. Puedes utilizar la función delay para hacer una pausa. Si pones justo después del if-else delay(1000) hará una pausa de un segundo en cada vuelta del bucle loop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Comprobar si está pulsado o no if(estadoPulsador == HIGH){ // Encender el LED digitalWrite(7, HIGH); Serial.println("LED encendido"); }else{ // Apagar el LED digitalWrite(7, LOW); Serial.println("LED apagado"); } // Pausa de un segundo delay(1000); } |
Y así de sencillo es controlar pines digitales de Arduino. Tan sencillo que lo puede hacer hasta un crío de 10 años.
Pero no te vayas todavía, aún hay más.
Cómo de rápido pueden ser los pines digitales de Arduino
Seguramente estés pensando que eso de la velocidad de los pines te importa menos que una farola apagada. Y no te falta razón. Salvo algún caso muy específico, esto no tiene mucha utilidad. O sí.
Sin embargo, con la demostración que vas a ver a continuación vas a entender un poquito más cómo funciona la plataforma de Arduino tanto a nivel de software como de hardware.
Atento porque lo que viene es interesante.
Vamos a partir de este experimento. Con el mismo esquema eléctrico que hemos empezado vamos a cargar este código.
1 2 3 4 5 6 7 8 9 10 11 |
void setup() { pinMode(7, OUTPUT); } void loop() { digitalWrite(7, HIGH); digitalWrite(7, LOW); delay(5); } |
Este código lo único que hace es establecer el modo del pin en salida (OUTPUT) en la función setup y luego establece un estado HIGH y LOW para al final hacer un retardo de 5 ms.
Vamos, nada del otro mundo. Pero fíjate bien en las sentencias que cambian el estado a HIGH y LOW. Están seguidas una después de otra. En teoría deberían tener un mínimo tiempo entre ellas.
Si lo analizamos con un analizador lógico de 8 canales (compatible con Selae) y el software PulseView, podemos comprobar que el tiempo que transcurre entre pasar de estado HIGH a estado LOW es de 3,18 us (microsegundos).
Un tiempo ridículo para nosotros, pero cuando hablamos de electricidad y de electrones moviéndose a la velocidad de la luz, este tiempo puede ser una eternidad.
¿Por qué si una sentencia está justo después de otra tarda este tiempo? Y lo más importante, ¿se puede reducir?
La respuesta a todo esto la encontramos en el código. Igual que antes, si abres el código con el IDE 2.0 o PlatformIO y pulsando la tecla Ctrl y haces clic sobre la función, abrirá un archivo que se llama wiring_digital.c.
En este archivo puedes ver la implementación de la función digitalWrite.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void digitalWrite(uint8_t pin, uint8_t val) { uint8_t timer = digitalPinToTimer(pin); uint8_t bit = digitalPinToBitMask(pin); uint8_t port = digitalPinToPort(pin); volatile uint8_t *out; if (port == NOT_A_PIN) return; // If the pin that support PWM output, we need to turn it off // before doing a digital write. if (timer != NOT_ON_TIMER) turnOffPWM(timer); out = portOutputRegister(port); uint8_t oldSREG = SREG; cli(); if (val == LOW) { *out &= ~bit; } else { *out |= bit; } SREG = oldSREG; } |
También puedes ver la implementación de las funciones pinMode y digitalRead.
Y aunque no entiendo muy bien el código puedo intuir que lo que hace es cambiar algún registro de la memoria del microcontrolador. Es más, si profundizas en las funciones a las que se llama desde digitalWrite llegarás a un punto donde puedes ver instrucciones a bajo nivel con código LPM (una especie de lenguaje máquina).
Es un buen punto de partida para bajar a las catacumbas del código. Eso sí, vete preparado porque el viaje da miedo :)
Lo que importa de todo esto es que en algún sitio está cambiando la memoria, los registros. Ahí, a cara perro. Y esto si que es interesante.
¿Te acuerdas de los 3 bloques en los que se dividen los pines del microcontrolador ATMega328p? ¿Los que empiezan por PB, PC o PD?
Pues ahora todo esto cobrará sentido. Atento a lo que viene a continuación.
Acceso a los pines digitales de Arduino por registros de memoria
Ya hemos visto que acceder y controlar los pines digitales de Arduino es relativamente sencillo con las funciones pinMode, digitalWrite y digitalRead. Sin embargo, quizás no sea la forma más rápida de hacerlo.
Aunque el tiempo que tarda es despreciable para un humano, hay algunos sistemas que requieren de señales que sean más rápidas. Ya vimos las señales de reloj y temporizadores con Arduino.
Si nos saltamos al intermediario (la implementación de la función) y accedemos a los registros de memoria directamente, quizás consigamos ser más rápidos. O no.
Pero antes de bajar al fango vamos a ver qué son los registros de memoria. O más bien, qué entiendo yo por los registros de memoria.
¿Qué son los registros de memoria de Arduino?
A ver, partimos de la base que yo no soy el más adecuado para hablar de este tema así que te lo voy a explicar como yo lo entiendo.
Cuando hablo de registro de memoria me refiero a la memoria SRAM, memoria volátil, es decir, que cuando la dejas de alimentar resetea los datos, se borran. Luego está la memoria EEPROM y la memoria Flash donde si guardas algo se mantiene aunque te quedes sin alimentación. Este tipo de memorias se llaman no volátiles.
Estos son los tres tipos de memorias de Arduino.
Los registros de memoria de los pines digitales de Arduino están en la memoria SRAM por lo tanto, ya sabemos que si se deja de alimentar Arduino la información del modo y del estado se resetean a sus valores por defecto.
Por eso es importante establecer el modo de los pines en la función setup, porque cuando se resetea o se vuelve a alimentar, se ejecuta de nuevo esta función. Y por este motivo también los pines digitales de Arduino comienzan siempre en modo entrada (INPUT), su valor por defecto.
Dentro de la memoria SRAM hay una parte reservada para almacenar los registros. No solo de los pines digitales de Arduino, hay registros de interrupciones o del comparador lógico por ejemplo.
Cada celda de memoria ocupa 1 byte, es decir, 8-bit. Esto quiere decir que puedes almacenar un número del 0 al 255. Suficiente como para almacenar un carácter por ejemplo (cada carácter se codifica con un número del 0 al 255 según el código ASCII).
Un registro puede ocupar 1 byte o varios bytes, es decir, una celda de memoria o varias.
El cero en binario se representa con 8-bit todos a cero 00000000 y el 255 todos unos 11111111. Esto es importante entenderlo para lo que viene ahora.
Los registros de memoria de los pines digitales de Arduino se sitúan entre la posición de memoria 0x0020 en hexadecimal (32 en decimal) y la 0x005F (95 en decimal).
Y, ¿cómo asociamos cada celda de memoria de 1 byte a los pines digitales de Arduino? Esto lo hacemos a través de los registros de memoria en los que se agrupan los pines: puertos B, C y D.
Registros de memoria de los pines digitales de Arduino
Volviendo al esquema del ATMega328p, dije que estos se agrupan según su nombre. Están los que empiezan por PB, por PC y por PD.
La distribución de los pines es la siguiente:
- PORTB: comprende los pines del PB0 al PB7
- PORTC: comprende los pines del PC0 al PC6
- PORTD: comprende los pines del PD0 al PD7
Cada puerto tiene como máximo 8 pines asociados. Esto no está hecho así por casualidad, que cada puerto solo agrupe 8 pines o menos se hace para que cada puerto pueda ser almacenado en un registro de memoria que ocupa una celda de 1 byte. 8 puertos, 8-bit.
Ahora imagina que quieres almacenar el estado de los pines digitales de Arduino que están en el puerto B. Si asignas cada estado a un bit del byte que ocupa el registro, podrás almacenar su estado en una celda de memoria a través del registro.
Puedes ver lo que quiero decir en el siguiente esquema.
Cada pin del microcontrolador ATMega328p tiene asociado un pin de la placa de Arduino. Y cada pin puede estar en un estado determinado o HIGH o LOW. Estos estados se almacenan en el registro con un 1 para HIGH y un 0 para LOW.
Empezamos con todo en estado LOW. Para poner un pin concreto en estado HIGH solo hay que cambiar el bit adecuado del registro. Por ejemplo, el pin 9 en estado HIGH dejaría el registro de esta forma.
Internamente el registro está conectado con el hardware lo que quiere decir que si cambias su valor, esto emite algún tipo de señal que activa o desactiva el pin dejando pasar o no la corriente. No es muy diferente a cómo funciona un relé con Arduino pero a menor escala.
Toda la información de cómo se estructuran los registros y los tipos de registro que existen está en la hoja de características técnicas del microcontrolador ATMEga328p. Un buen sitio para pasar horas vagabundeando por la electrónica.
En la página 59 de la hoja de características puedes ver cómo se estructuran los pines digitales de Arduino a nivel de registros.
Nos dice que para cada pin vamos a tener 3 registros que se llaman DDxn, PORTxn y PINxn.
El registro DDxn se utiliza para controlar el modo de los pines. Un 1 equivale al modo salida (OUTPUT) y un 0 equivale al modo entrada (INPUT).
El registro PORTxn sirve para dos cosas. Cuando el pin está en modo salida (OUTPUT) almacena el estado del pin. Un 1 indica que está en estado HIGH y un 0 en estado LOW. Pero si el pin está configurado en modo entrada (INPUT), sirve para activar la resistencia interna pull-up con un 1 y desactivarla con un 0.
Y el registro PINxn sirve para invertir el estado del pin. Con un 1 cambias de valor el pin, si está en HIGH lo pasa a LOW y si está en LOW cambia a HIGH. Pero también sirve para obtener su estado independientemente del modo en el que esté configurado el pin.
Por este motivo si uno de los pines digitales de Arduino está configurado como entrada, podemos utilizar la función digitalRead para leer su estado. En estos casos se accede al registro PINxn para saber su estado.
Si avanzas más en la hoja de características técnicas, a la página 72, hay una descripción a nivel de bit de cada registro de los pines digitales de Arduino. Veamos la del puerto B por ejemplo.
Este mapa de memoria nos está diciendo que el pin B0 del microcontrolador (que coincide con el pin 8 de Arduino) se sitúa en el bit menos significativo del registro. Si cambias el valor del registro DDRB de 00000000 a 00000001 estarás configurando el pin 8 de Arduino en modo salida (OUTPUT).
Y si cambias el registro PORTB de 00000000 a 00000001 estarás cambiando el estado del pin de LOW a HIGH. Como ves todo tiene su lógica y sentido.
Pero es que aún hay más, desde el propio código de Arduino podemos acceder directamente a los registros y así saltarnos las funciones pinMode, digitalWrite y digitalRead.
¿Será más rápido acceder así a los pines digitales de Arduino? Comprobémoslo.
Acceso a los registros de los pines digitales de Arduino
En contra de lo que puedas pensar, acceder a los registros de los pines digitales de Arduino es bastante sencillo (como todo en la plataforma de Arduino). Ellos ya pensaron que esto podría ser útil y habilitaron unas variables que almacenan estos registros.
Puedes acceder a cada uno de los registros a través de las siguientes variables definidas en el archivo iom328p.h:
- Registro B: PORTB, DDRD, PINB
- Registro C: PORC, DDRC, PINC
- Registro D: PORD, DDRD, PIND
Con sólo asignar un nuevo valor en formato binario a estas variables estarás modificando la configuración de los pines digitales de Arduino. Veamos cómo encender y apagar el LED que tenemos conectado al pin 7 de Arduino.
Lo primero es buscar en qué registro está. Según el mapeo de pines está conectado al pin PD7 que pertenece al registro del puerto D.
Ahora hay que ir a la hoja de características técnicas y comprobar en que posición del registro se sitúa este pin.
Es justo el bit más significativo del byte. Por lo tanto, para establecer el modo salida (OUTPUT) hay que cambiar este bit a 1 en el registro DDRD.
Por otro lado, para encender el LED hay que cambiar el mismo bit a 1 en el registro PORTD y si quieres apagarlo lo estableces a 0 en el mismo puerto.
Todo esto se traduce a las siguientes líneas de código.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
void setup() { // Configurar pin 7 en modo salida (OUTPUT) DDRD = B10000000; } void loop() { // Establecer estado HIGH PORTD = B10000000; delay(1000); // Establecer estado LOW PORTD = B00000000; delay(1000); } |
Como ves es muy sencillo, sólo tienes que asignar un nuevo valor en binario. Esto se hace poniendo la letra B y seguido los 8-bit del registro. Si subes el código podrás ver el LED parpadear.
Los registros no son exclusivos de los microcontroladores de Arduino, muchos sensores funcionan con registros y otros microcontroladores como el ESP8266 también.
Solo nos queda una duda que resolver, ¿es más rápido acceder a los pines digitales de Arduino por los registros o por las funciones pinMode, digitalWrite y digitalRead?
Acceso rápido a los pines digitales de Arduino
Ya hemos comprobado que accediendo con digitalRead el acceso es muy rápido pero que quizás, para ciertas aplicaciones, no lo sea tanto. Ahora le toca el turno a los registros de los pines digitales de Arduino.
Voy a utilizar el siguiente código.
1 2 3 4 5 6 7 8 9 10 11 12 |
void setup() { // Configurar pin 7 en modo salida (OUTPUT) DDRD = B10000000; } void loop() { // Establecer estado HIGH PORTD = B10000000; // Establecer estado LOW PORTD = B00000000; delay(5); } |
Es igual que en la prueba anterior pero ahora utilizando los registros. Con el analizador lógico de 8 canales se obtiene que la velocidad de acceso es de 0,0625 us.
Recuerda que con las funciones de Arduino el tiempo era de 3,18 us. La paliza es monumental :)
Y ahora sí, doy por finalizado este tutorial. Espero te haya servido para entender mejor Arduino y cómo funciona internamente un microcontrolador. Solo quedaría una cosa por explicar las operaciones lógicas para el manejo de registros de memoria. Lo dejo pendiente hasta que esta entrada tenga 15 comentarios.
Cuando esto suceda, complemento el artículo con esta sección.
Cualquier duda o sugerencia en los comentarios de aquí abajo.