Un robot que procura moverse tan bien como una hormiga

Los insectos en general son infaliblemente impresionantes por lo inteligentes y capaces que son, con un mínimo absoluto de detección y poder de cómputo. Cuando las cosas empiezan a ponerse realmente interesantes es cuando los insectos tienen que volverse inteligentes para manejar entornos especialmente difíciles. Las hormigas del desierto son un gran ejemplo de esto: mientras que la mayoría de las hormigas dependen de los senderos de las feromonas para navegar (recorren los senderos de su olor para regresar al nido), el calor del desierto significa que las feromonas no duran mucho. En cambio, las hormigas del desierto se basan en una variedad de técnicas, que incluyen el conteo de pasos, el flujo óptico, los puntos de referencia y, especialmente, la navegación solar.

Parece que estas técnicas podrían ser útiles para los robots pequeños y económicos que exploran el sistema solar, donde el GPS no está disponible y los sensores sofisticados vienen con un presupuesto de masa y energía para igualar. En Science Robotics, los investigadores describen cómo construyeron un robot con herramientas de navegación inspiradas en las hormigas del desierto, y pudieron hacer que deambulara un poco y que encontrara su camino a casa sin GPS, SLAM o algo un poco más complejo.

Lo primero que hay que entender es cómo navegan las hormigas del desierto. En general, el sistema que utilizan se denomina “integración de ruta”, que es esencialmente el mismo que llamaríamos “recuento muerto”. Al hacer un seguimiento de las distancias y direcciones recorridas en el tiempo, las hormigas pueden calcular la ruta más directa hacia atrás. a donde empezaron

Básicamente, si la hormiga se dirige al norte por un tiempo, y luego al este por el doble de tiempo, sabe que al viajar al sur y al oeste (el doble de largo), terminará bastante cerca de su posición inicial, y una vez que esté cerca, Puede reconocer visualmente puntos de referencia para volver exactamente a su nido.

Las hormigas del desierto son notablemente buenas en esto, como lo muestra la siguiente figura. Después de un viaje de ida y vuelta de casi 600 metros a lo largo de unos 20 minutos, la hormiga de un centímetro de longitud puede trazar una línea más o menos exactamente recta directamente a su nido en solo seis minutos.

Imagen: Aix Marseille University / CNRS / ISM Una hormiga del desierto C. fortis usa la “integración del camino” para encontrar su camino de regreso a su nido. La línea delgada muestra la trayectoria de salida (592.1 metros), con pequeños puntos negros que representan marcas de tiempo (cada 60 segundos). La hormiga regresó directamente a su nido (línea gruesa, 140.5 m de largo). El círculo pequeño (abajo a la derecha) marca la entrada del nido, y el negro grande muestra la ubicación de alimentación (centro superior).

Para que la integración del camino funcione, la hormiga debe rastrear dos cosas separadas: la distancia y la dirección. La distancia es la más fácil por mucho, ya que la hormiga puede usar una combinación (muy familiar para los robots) de conteo de zancadas y flujo óptico. La dirección es complicada: es bien sabido que las hormigas y otros insectos pueden usar el Sol para navegar, rastrear su ubicación en el cielo y corregir la rotación de la Tierra y el consiguiente movimiento aparente del Sol a lo largo del tiempo. Esto solo funcionaría cuando está realmente soleado, excepto que los ojos de las hormigas tienen fotorreceptores que son sensibles a la luz polarizada, lo que puede indicar la dirección del Sol, incluso si está nublado. Las hormigas también son sensibles a los rayos UV, lo que les ayuda a ver el Sol a través de la capa de nubes y el follaje.

Imagen: Aix Marseille University / CNRS / ISMAntBot es manejado por una placa Raspberry Pi 2B y sus sensores incluyen una brújula celeste, IMU y sensor de flujo óptico.

AntBot es un intento de replicar los sistemas de detección de las hormigas del desierto para ver qué tan bien un sistema autónomo podría usarlas para la navegación inspirada en las hormigas. AntBot es un hexápodo de 2,3 kilogramos, cuyas especificaciones físicas específicas no son realmente tan importantes para los fines de esta investigación. Lo que es importante son los sensores de AntBot, que incluyen un sensor óptico de flujo bioinspirado y una “brújula celeste inspirada en insectos” que consiste en un par de sensores de luz UV con polarizadores lineales giratorios. La brújula analiza la relación logarítmica entre los datos de estos dos sensores para determinar el ángulo de polarización de la luz entrante, que utiliza para determinar dónde está el Sol y, por lo tanto, en qué dirección está apuntando. AntBot puede hacerlo con mucha precisión: El error mediano fue de solo 0,02 ° cuando el cielo estaba ligeramente nublado, 0,59 ° bajo un cielo nublado.

Al combinar el seguimiento de la distancia del flujo óptico, el conteo de pasos y la navegación celeste al igual que la hormiga del desierto, probablemente no le sorprenda saber que AntBot pudo deambular repetidamente al azar en una distancia de unos 14 metros y luego regresar con éxito a su punto de partida. Esto es bueno, pero AntBot aún tiene mucho trabajo por hacer para demostrar que es tan talentoso como una hormiga, como señalan los investigadores:

“En su forma actual, AntBot tiene un diámetro de 45 cm y caminó a una velocidad de unos 10 cm/s durante los experimentos, mientras que las hormigas C. fortis desert tienen solo 1 cm de largo. Como se muestra en la Fig. 1A, la trayectoria de la hormiga mide 732,6 m. Por lo tanto, AntBot debería haber cubierto más de 32 km para poder compararlo adecuadamente con el rendimiento de navegación de las hormigas. Aunque AntBot puede caminar a velocidades de hasta 90 cm s, la navegación a gran escala requerirá mejorar los actuadores y la fuente de alimentación del robot hexápodo. Estas mejoras permitirán probar el modo PI-Full en contextos más naturales, como los terrenos escarpados en un entorno saturado (bosques) donde la vista del cielo a menudo está inhibida por la presencia de ramas y hojas en el campo visual de la brújula celeste.”

Es posible que los insectos hayan sido los primeros en descubrir este truco de luz polarizada, pero es posible que los humanos hayan estado usando una técnica similar para ayudarlos a navegar durante siglos. Hay algunas pruebas que sugieren que los vikingos (así como las culturas marineras posteriores que probablemente tuvieron la idea de los vikingos) podrían haber confiado en la luz polarizada para encontrar la ubicación del sol bajo un cielo cubierto usando una piedra solar, una de una pequeña cantidad de minerales que son birrefringentes. Los minerales birrefringentes son polarizadores, y cuando la luz entra en ellos, se divide en dos rayos que toman diferentes caminos a través de la piedra dependiendo de donde la fuente de luz es relativa a la piedra. Al mirar a través de la piedra hacia el cielo, es posible usar la birrefringencia para determinar dónde está el Sol con una precisión de unos pocos grados, incluso si está completamente nublado, o si el Sol está por debajo del horizonte. Todo lo que se necesita es un poco de luz solar, y una piedra solar funcionará.




El mineral birrefringente más común es la calcita, y los vikingos habrían tenido acceso a eso. Algunas leyendas vikingas se refieren directamente a las piedras solares, y las simulaciones han demostrado que el uso de una piedra solar podría haber tenido un gran impacto en la capacidad de los vikingos para realizar viajes prolongados a través del océano abierto. Los barcos vikingos y los sitios de entierro no han producido mucha calcita, pero es más frágil que los minerales y no necesariamente duraría tanto tiempo bajo el agua o en el suelo. Y si no terminaron usando algo como esto para navegar, bueno, realmente deberían haberlo hecho, porque tanto las hormigas como los robots están obteniendo grandes resultados con eso.

“AntBot: A six-legged walking robot able to home like desert ants in outdoor environments”
(“AntBot: un robot andante de seis patas capaz de vivir como hormigas del desierto en ambientes al aire libre”)
, por J. Dupeyroux; JR Serres; S. Viollet en la Universidad Aix de Marsella en Marsella, Francia, aparece en la edición actual de Science Robotics.


Comprendiendo Variables al programar en C

Este es un análisis de la naturaleza y el uso de las variables en lenguaje C en el contexto de las aplicaciones para microcontroladores

Muchos de nosotros escuchamos la palabra “variable” en las clases de matemáticas mucho antes de saber mucho, si es que sabemos algo, acerca de la programación de computadoras. Una variable matemática es una cantidad cuyo valor no se conoce o no se limita a un número. Este uso es similar, aunque no idéntico, al concepto de una variable C.

Dos diferencias importantes: primero, en matemáticas, usualmente usamos una letra como x o y para representar una variable, mientras que en C usamos frecuentemente una palabra o frase descriptiva como contadorPulsos, velocidadMedia o cantidadVueltas. En segundo lugar, hay situaciones en las que usamos una variable de C para identificar una cantidad conocida y que no se pretende que cambie del valor original.

Variables en hardware

Las variables son convenientes e intuitivas para los programadores. Para el hardware computacional, por otro lado, no tienen un significado real. Los microprocesadores almacenan datos en registros y ubicaciones de memoria. Esta diferencia fundamental entre las personas que escriben el idioma de máquina y las máquinas que ejecutan este programa se supera mediante lenguajes de alto nivel como C, que maneja varios detalles asociados con la traducción entre variables basadas en texto y la realidad física de un procesador.

Los diseñadores de sistemas integrados trabajan a menudo con procesadores de 8 bits. En estos dispositivos, por lo general, el tamaño fundamental de los datos es un byte. La memoria se organiza de acuerdo con los bytes, el tamaño de los registros es de un byte y la CPU está diseñada para procesar datos de 8 bits. Esta es una limitación bastante incómoda porque hay muchas situaciones en las que el valor de una variable excederá el valor máximo de un número de 8 bits.

Finalmente, todas las variables C cuidadosamente definidas y con un nombre ilustrativo terminan como bits en la memoria (o registros)

El lenguaje C no limita el tamaño de una variable a 8 bits, incluso cuando está trabajando con un procesador de 8 bits. Esto significa que una variable en el programa en lenguaje de máquina que ejecuta el microprocesador puede corresponder a múltiples registros o ubicaciones de memoria en el hardware. “Manualmente” administrar variables de múltiples bytes (es decir, a través del lenguaje ensamblador) no parece muy deseable, pero a los compiladores no les importa en absoluto, y hacen el trabajo muy bien.

Definiendo variables

El primer paso para usar una variable es definir esa variable. Los componentes esenciales de una definición de variable son el tipo y el nombre.

Hay muchos tipos de variables; La lista completa, así como los detalles de la implementación del hardware, variarán según el compilador que esté utilizando. Aquí hay unos ejemplos:

char : un valor con signo de un byte
int : un valor con signo de dos o cuatro bytes
long : un valor con signo de cuatro bytes
float : un valor de cuatro bytes que puede tener números después del punto decimal; en otras palabras, no está limitado a enteros
bit : el valor de la variable puede ser cero o uno

Esta es una representación visual de cómo una serie de bits se interpreta de manera diferente en función de si una variable se considera con signo (utilizando la notación de complemento a dos) o sin signo:

El siguiente código muestra definiciones de variables que consisten solo en un tipo básico y un nombre (la forma más técnica de referirse al nombre es “identificador”):

Inicializando variables

En muchos casos, es una buena idea darle un valor inicial a una variable. Esto facilita la depuración y es esencial si la variable se utilizará antes de que le fije un valor conocido. Puede inicializar una variable en la definición, o en otra parte de su código, pero incluir el valor inicial en la definición es una buena manera de mantener el código organizado y desarrollar el hábito de inicializar siempre que sea necesario.

Aquí hay ejemplos de definiciones de variables que incluyen una inicialización:

Definiciones de variables delicadas

Hay varias otras palabras que se pueden incluir en una definición de variable. Estos se utilizan para especificar con mayor precisión la naturaleza de la variable o para dar instrucciones al compilador sobre cómo implementar la variable en el hardware.

Las siguientes palabras clave podrían ser útiles en sus proyectos de programa para microcontroladores:

■ unsigned : como habrá adivinado, esto le dice al compilador que interprete la variable como un valor sin signo en lugar de un valor con signo. Uno defino le mayoría de sus variables como sin signo, porque rara vez se necesitan números negativos.

■ const : el calificador de tipo const indica al compilador que el valor de una variable no debe cambiar. Como dice al principio del artículo, a veces el valor de una “variable” C no es variable. Si usted comete un error en su código e intenta modificar el valor de una variable const, el compilador generará un error.

■ volatile : los compiladores sofisticados no solo toman su código original y lo traducen directamente al lenguaje de máquina. También intentan hacer que el código funcione de manera más eficiente, y este proceso se conoce como “optimización”. En general, la optimización es algo bueno. Sin embargo, de vez en cuando, puede arruinar el día, porque el compilador se optimiza solo en función del código y no puede tener en cuenta los eventos de hardware que interactúan con el código. Cuando una variable tiene el calificador de tipo volátil, el compilador sabe que debe tener cuidado con las optimizaciones relacionadas con esa variable.

Una interrupción puede hacer que el valor de una variable se modifique de una manera que el compilador no espera, y esto puede llevar a una optimización problemática

■ tipos de memoria, como xdata , idata y code : estas palabras obligan al compilador a ubicar una variable en una parte específica de la memoria del microprocesador. El tipo que aloja en la memoria de programa es particularmente útil: los recursos de RAM en un microcontrolador a menudo son mucho más limitados que la memoria de programa no volátil, y el tipo de memoria de código le permite utilizar memoria de programa adicional para almacenar datos que se usan en su programa pero nunca se modifican.

Aquí hay unos ejemplos:

Uso de las variables

No hay mucho que decir acerca de cómo usar sus variables después de que se hayan definido. En realidad, con respecto a la variable en sí, la definición es la mayor parte del trabajo. Después de eso, usted simplemente incorpora el identificador de la variable en operaciones matemáticas, bucles, llamadas a funciones, etc. Un buen compilador no solo manejará los detalles de la implementación del hardware, sino que también buscará formas de optimizar el código con respecto a la velocidad de ejecución o el tamaño del programa.

Quizás el error más común relacionado con el uso variable es un desbordamiento. Esto se refiere a una situación en la que el valor asignado a una variable está fuera del rango numérico asociado con el tipo de datos de la variable.

Se debe pensar en todos los escenarios posibles relacionados con una variable determinada y luego elegir el tipo de datos en consecuencia.

Resumen

La funcionalidad de la variable básica proporcionada por el lenguaje C es intuitiva y directa, pero hay algunos detalles que pueden ayudarlo a hacer que una aplicación integrada sea más confiable y eficiente. Si tiene alguna duda relacionada con las variables de C, no dude en preguntar en nuestro grupo en Facebook Robots Didácticos.




Ampliar la cantidad de entradas de Arduino

Para expandir la capacidad de entradas digitales se utilizan registros de desplazamiento (Shift Register en inglés) con entradas en paralelo y salida serie.

Para este ejemplo, utilizamos tres pines digitales de la placa Arduino en conjunto con el circuito integrado 74HC165 (puede ser, también, 74LS165), que nos aportará 8 entradas.

Diagrama de pines:

Se pueden conectar varios de estos chips en cadena, lo que le nos aporta 16, 24, 32 o más entradas, sin usar ningún pin digital adicional de Arduino, simplemente conectando la salida QH de un registro a la entrada de datos serie SER del otro.

En el diagrama que sigue se ve cómo es la conexión completa de dos 74HC165. Los conjuntos de llaves de corredera, llamados DIP Switch en inglés, representan a las diversas entradas que se podrían ingresar. Si son llaves que se cierran (pulsadores, microswitches, relés, salidas de colector abierto), corresponde el circuito tal como se lo ve, con un resistor de polarización (pull-up) conectado a +5V. Si se trata de salidas digitales de otros chips y placas con salidas compatible con lógica TTL, no es necesario colocar el resistor.

Con línea de puntos se indica la conexión para continuar la cadena indefinidamente. La señal CLK (pata 2) va unida entre chips, e igual la señal SH/LD (pata 1), con la única salvedad de que hay que leer la hoja de datos del microcontrolador (según cuál sea) para saber cuántas entradas de este tipo de chip TTL HC se le pueden conectar a una salida digital de Arduino.

La tabla que sigue, y el diagrama interno del chip, los agrego para ofrecer más información y mejor comprensión del funcionamiento. Se pueden encontrar más detalles en su hoja de datos.

Diagrama de tiempo

La señal SH/LD se mantiene en ALTO durante las operaciones de desplazamiento. Un pulso a BAJO en este pin ingresa nuevos datos desde las entradas al registro de desplazamiento. Cuando el pulso vuelve a ALTO, queda todo dispuesto para aplicar pulsos de CLK y desplazar uno a uno los bits del registro hacia la salida serie QH. Si hemos usado un solo chip 74HC165, con 8 pulsos se habrán leído todas las entradas a través de QH (luego de haberlo conectado a una entrada digital del Arduino). Cada chip adicional requiere 8 pulsos más. En los programas se puede entender claramente cómo se ingresan los datos a variables dentro de nuestro microcontrolador.

Leer una de las entradas del 74HC165 – Diseño del programa:

■ Se aplica un pulso de alto a bajo y luego de regreso a alto en SH/LD, y así las entradas ingresan al registro de desplazamiento (condición “LOAD”, o de carga).

■ Aplicar a la salida CLK la cantidad de pulsos necesarios, desplazando los datos, para que el bit que deseamos leer quede ubicado en la línea de salida QH.

■ Leer el valor del pin digital de entrada del Arduino.

Primer programa de prueba

Este programa lee de a un pin e indica su estado en el LED incluido en el Arduino UNO, pin 13 = LED_BUILTIN. Para determinar qué entrada queremos leer para conocer su estado, se lo indicamos tipeando los números 1 a 8 desde el teclado de la computadora a la que esté conectado el Arduino por USB.

Incluso, usando comparaciones con IF se podría elegir como comando cualquier caracter. Puede ser “a”, “b”, “c”, o “A”, “B”, “C”, o lo que usted elija. Si la entrada proviene de sensores de puertas o ventanas abiertas, o de luces encendidas, se puede usar, por ejemplo, “C” para cocina, “B” para baño, “P” para pasillo, “p” para patio, “E” para entrada, “H” para una habitación y “h” para otra habitación. Y así. Además, estas letras de comando se pueden enviar a través de un módulo Bluetooth HC-06 o HC-05 y controlar desde el teléfono celular, recibiendo la respuesta por el mismo medio.

En este programa —para simplificar— se eligió tipear un número desde el teclado para leer cada entrada. Lo más interesante es la función que lee las entradas y las desplaza hacia la salida. Se le envía como parámetro un valor numérico y entero de 1 a 8, y devuelve un 1 o un 0 según el estado de la entrada. No es difícil ampliar el código hasta la cantidad de entradas que desee.

Agregando esta función a su programa, puede usarla del modo que usted quiera.

Utilizamos este circuito, que es el mismo que servirá para todos los programas excepto el de manejo por SPI, que requiere conectar dos señales del circuito a las entradas MOSI y SCLK de esta interfaz, como luego veremos.

Disculpen si tiene un aspecto un poco antiestético, pero ocurre que es difícil cablear un conjunto de llavecitas DIP al chip 74HC165 con el programa de dibujo sin hacer cruces de cables, y que sea todo visible. Ya verán en la foto que el circuito real, en la protoboard, quedó más prolijo.

Programa para leer de a una entrada

 


 
Segundo ejemplo: ingresar todas las entradas a variables

El siguiente programa permite listar en el Monitor Serie el estado de los pines de entrada del circuito del ejemplo, o de una cadena de chips para ampliar 16, 24, 32 o más entradas. Tampoco necesita biblioteca. El listado se actualiza cada vez que cambia una entrada. Los comentarios explican en detalle su funcionamiento.

El Monitor Serie mostrará este mensaje, y se renovará cada vez que se cambie el valor de una entrada.


 


 
Biblioteca ShiftIn

Esta es una biblioteca que permite leer 8 o más entradas. Y también, si bien la biblioteca tiene una función que define 4 pines, se pueden usar sólo 3 pines al Arduino. Además, se puede conectar en cadena varios registros de desplazamiento utilizando la misma cantidad de pines digitales, o sea 3. El cableado es con la misma configuración que en el diagrama de protoboard mostrado arriba.

Instalación fácil (importar zip)

La forma más fácil de instalar esta biblioteca es descargar la última versión y luego importarla. No tiene que descomprimirlo. Simplemente abra su IDE de Arduino y navegue a Programa > Incluir Librería > Añadir Biblioteca .ZIP… y luego seleccione el archivo zip, que puede bajar desde aquí.

Instalación manual

Por supuesto también se puede instalar esta biblioteca manualmente. Para hacerlo, descargue la versión más reciente y descomprímala. Luego tiene que copiar la carpeta ShiftIn (NO la carpeta ShiftIn-x.y.z) y colocarla en la carpeta de la biblioteca Arduino:

Windows: Documentos\Arduino\libraries\
Mac and Linux: Documents/Arduino/libraries/

Después de esto solo hay que reiniciar el IDE de Arduino.

Uso de ShiftIn

Si ha instalado esta biblioteca, puede incluirla navegando a Programa > Incluir Librería > ShiftIn. Esto agregará la línea #include <ShiftIn.h> a su programa (por supuesto, también puede escribir esta línea manualmente).

Ahora se puede usar esta biblioteca:

Los datos en el Monitor Serie se verán así:

Si usted necesita usar dos shift registers, sólo tiene que cambiar la declaración de ShiftIn<1> shift; a ShiftIn<2> shift;, y así sucesivamente.

El diagrama para 2 chips – 16 entradas es este:

Detalle de las funciones de la biblioteca ShiftIn

Dependiendo de la cantidad de chips, esta biblioteca utilizará diferentes tipos de datos. Si solo está utilizando un chip, el tipo ShiftType será un unsigned byte (uint8_t). Para dos chips será un unsigned int (uint16_t). Para tres o cuatro chips será un unsigned long (uint32_t) y para 5 a 8 chips será un unsigned long long (uint64_t). La biblioteca todavía no maneja más de ocho chips.

Esta función debe ser llamada en la función de configuración. Se utiliza para indicar a la biblioteca los pines que debe usar.

void begin(int ploadPin, int clockEnablePin, int dataPin, int clockPin)

GetPulseWidth() define el retardo para el pin de clock en microsegundos. Este valor está fijado en 5 us y en general no habrá necesidad de cambiarlo, aunque teniendo en cuenta el tiempo de programa transcurrido entre la ejecución de el inicio del pulso y de su final, puede ser tan pequeño como 1 us, incluso 0.

uint8_t getPulseWidth()
void setPulseWidth(uint8_t value)

Retorna la cantidad de entradas (bits en el estado)
uint16_t getDataWidth()

Retornan VERDADERO si ha cambiado alguna entrada durante la última lectura.
boolean hasChanged()
boolean hasChanged(int i) // lo mismo de arriba, pero solo para la entrada i

Retornan el estado completo del actual y el último grupo de bits
ShiftType getCurrent()
ShiftType getLast()

Retornan el estado de una sola entrada en el grupo actual de bits y el último grupo de bits
boolean state(int i)
boolean last(int i)

Indica cuando una entrada ha cambiado. En el ejemplo de los esquemas, se ha presionado o se ha soltado un pulsador
boolean pressed(int id) // no estaba presionado en la última lectura, pero ahora sí
boolean released(int id) // estaba presionado en la última lectura, pero ahora fue liberado

Esta función (la función para actualizar) debe ser llamada una vez por cada grupo de bits. Leerá todos los valores de los shift registers y retornará el nuevo estado.
ShiftType read()

Esta función es básicamente la misma que la función read, pero retorna VERDADERO si ha cambiado de estado alguna entrada, y FALSO en el caso contrario
boolean update()
 


 
Ingresando datos por SPI

Conectando el 74HC165 a la interfaz SPI se puede leer rápidamente 8 entradas digitales, ingresando los 8 bits en una sola instrucción de SPI. Se los puede encadenar para leer 16, 24, 32 o más entradas a la vez.

Podría usarse, por ejemplo, para examinar la configuración de un interruptor DIP de 8 interruptores (donde se fijaría la configuración del dispositivo).

En el 74HC165 se tiene que pulsar el pin de “cargar” (pin 1 del chip) para que el registro ingrese las entradas externas en sus registros internos.

La habilitación del chip, en esta prueba, es fija. El pin de activación de chip (/CLK INH) está a tierra, ya que el chip también puede funcionar estando siempre habilitado. Tenga en cuenta que la línea MISO siempre está activa, por lo que no es posible compartir este chip con otros dispositivos SPI utilizando el hardware SPI. Se puede solucionar agregando un circuito selector, pero no lo describiré en este artículo ya que si necesita usar otro dispositivo SPI, puede utilizar los otros ejemplos de programa, sin necesidad de SPI.

Código de programa

Es fácil de leer desde el registro. La biblioteca SPI se ocupará de todo.

Los pasos importantes son (dentro de la función de bucle):

1. Aplique un pulse al pin de carga paralela para cargar el registro desde las entradas.
2. Haga una transferencia SPI para leer el registro del chip.

Si desea leer más de 8 interruptores, simplemente use más registros, conecte en paralelo los pines 1, 2 y 15 de los chips, y la salida de cada chip “anterior” en la secuencia (QH) a la entrada del siguiente (SER).

Para leer cuatro bancos de interruptores, se cambia la sección de lectura del programa:

digitalWrite(LATCH, LOW);
digitalWrite(LATCH, HIGH);
bancoEntradas1 = SPI.transfer(0);
bancoEntradas2 = SPI.transfer(0);
bancoEntradas3 = SPI.transfer(0);
bancoEntradas4 = SPI.transfer(0);

NOTA: OBSERVE QUE HAY QUE CAMBIAR, EN EL DIAGRAMA DEL PROTOBOARD DE ARRIBA, LOS PINES QUE IBAN A SALIDA DIGITAL 11 Y SALIDA DIGITAL 12 DEL ARDUINO. LA CONEXIÓN QUE ESTABA EN 12 SE DEBE PASAR A 13, Y LA QUE ESTABA EN 11 SE DEBE PASAR A 12.

Resultados en Monitor Serie. Muestra una indicación del estado al inicio, y luego lista cada cambio que se produce en las entradas:


 


 
Otras opciones

Por último, existen dos librerías relacionadas, llamadas bitBangedSPI y bitBangedSPIfast, que permiten operar las múltiples entradas desde 74HC165 con un SPI implementado por software, y dejar libre el módulo SPI de hardware para otros dispositivos. Entiendo que es lo que hicimos con los programas de ejemplo anteriores, pero les dejo la inquietud de investigarlos y probarlos.


Arduino: alojar datos en la memoria de programa con PROGMEM

Almacene los datos en la memoria FLASH (memoria de programa) en lugar de RAM (a veces nombrada como SRAM, del inglés STATIC RAM = RAM estática).

Los distintos tipos de memoria disponibles en una placa Arduino son:

FLASH: Esta memoria almacena el código del programa.

RAM: Esta memoria almacena los datos del programa y es volátil. Cuando se apaga Arduino se pierden los datos.

EEPROM: Esta memoria almacena datos que son persistentes. Esta memoria puede ser utilizada para guardar configuración o cualquier otro dato que podamos utilizar si se apaga y enciende Arduino.

La palabra clave PROGMEM es un modificador de variable. Se debe usar solo con los tipos de datos definidos en la biblioteca pgmspace.h.

PROGMEM le dice al compilador “ponga esta información en la memoria flash”, en lugar de en la RAM, donde iría normalmente.

PROGMEM es parte de la biblioteca pgmspace.h. Ésta se incluye automáticamente en las versiones modernas del IDE. Sin embargo, si estuviese utilizando una versión IDE por debajo de 1.0 (2011), primero deberá incluir la biblioteca en la parte superior de su programa, con esta declaración:

Sintaxis

tipoDato – algún tipo de variable
nombreVariable – el nombre de su matriz de datos

Tenga en cuenta que debido a que la indicación PROGMEM es un modificador de variable, no existe una regla estricta sobre dónde debe ir, por lo que el compilador acepta diversas posiciones, que también pueden ser sinónimos. Sin embargo, las pruebas han indicado que, en varias versiones de Arduino, PROGMEM puede funcionar en una ubicación y no en otra.

Los siguientes ejemplos se han probado y funcionan con los IDE actuales. Las versiones anteriores del IDE pueden funcionar mejor si la palabra PROGMEM se incluye después del nombre de la variable.

Tenga en cuenta que dado que PROGMEM se podría usar con una sola variable, pero en realidad solo vale la pena si tiene un bloque de datos más grande para almacenar, que por lo general es más fácil de poner en una matriz (o en otra estructura de datos C++, algo que queda más allá de la presente discusión).

Usar PROGMEM es un procedimiento de dos pasos. Después de tener los datos que están en la memoria Flash, se requieren métodos especiales (funciones), también definidos en la biblioteca pgmspace.h, para volcar los datos de la memoria de programa a la RAM, de modo de que podamos trabajar con ellos.

Código de ejemplo

Las siguientes secciones de código ilustran cómo leer y escribir unsigned char (bytes) e int (2 bytes) en PROGMEM.

Matrices de cadenas de caracteres

A menudo es conveniente configurar una serie de cadenas de caracteres cuando se trabaja con grandes cantidades de texto, como por ejemplo en un proyecto con una pantalla LCD.

Debido a que las cadenas en sí son matrices, en realidad este es un ejemplo de una matriz bidimensional.

Estas tienden a ser grandes estructuras, por lo que por lo general es deseable colocarlas en la memoria del programa.

El siguiente código ilustra la idea.

Notas y advertencias

Tenga en cuenta que las variables deben estar definidas globalmente, o definidas con la palabra clave static, para poder trabajar con PROGMEM.

El siguiente código NO funcionará cuando esté dentro de una función:

El siguiente código funcionará, incluso si está definido localmente dentro de una función:

La macro F()

Cuando se utiliza una instrucción como:

La cadena a imprimir normalmente se guarda en la memoria RAM. Si su programa imprime muchas cosas en el Monitor Serie, puede acabar llenando la RAM. Si tiene espacio libre en la memoria FLASH, puede indicar fácilmente que la cadena debe guardarse en FLASH, usando la sintaxis:




Uso de la EEPROM de Arduino

La memoria EEPROM (del inglés Electrically Erasable Programmable Read-Only Memory = ROM programable y borrable eléctricamente) es una memoria no volátil. Esto significa que los datos que almacena no se pierden al desaparecer la alimentación de un dispositivo.

La EEPROM fue pensada para mantener aquellos datos que deseamos resguardar luego de apagar y reiniciar un microcontrolador, y disponer de ellos al retomar la operación.

El ATmega328P —el microcontrolador del Arduino UNO, el Nano y otros de la línea Arduino— tiene una capacidad de 1.024 bytes (1 Kb) de memoria EEPROM. La hoja de datos nos indica que la EEPROM del ATmega328P acepta hasta 100.000 ciclos de lectura/escritura. Parece mucho, pero el solo hecho de tener una cantidad de ciclos de escritura acotada ya nos indica que su función no es en nada similar a una RAM. Es para guardar datos, y no en forma momentánea y veloz, sino de manera durable. No ponga nunca operaciones con la EEPROM dentro de la función loop(), ya que, dada la velocidad de trabajo del microcontrolador, llegará rápidamente al límite de 100.000.

Tengamos también en cuenta otro dato importante: una operación de escritura en la EEPROM requiere 3,3 ms para completarse frente a la velocidad miles de veces superior de la RAM, capaz de operar a 20 MHz.

Para acceder a la memoria EEPROM debemos usar la librería EEPROM disponible de manera estándar desde el IDE de Arduino. La biblioteca —o librería— se llama EEPROM.h, y se debe incluir al inicio del programa.

Funciones en la biblioteca EEPROM:

FUNCIÓN EEPROM.read()

Lee un byte de la posición de memoria que indica su parámetro. De fábrica, todas las posiciones de memoria tienen escrito el valor 255 (0xFF).

Sintaxis:

      EEPROM.read(direccion)

Parámetros:

direccion: la posición de memoria (de 0 a 1023 = 1024).

Ejemplo con EEPROM.read(), leer toda la EEPROM y mostrarla en Monitor Serie

FUNCIÓN EEPROM.write()

Esta función escribe un byte en la posición indicada de la EEPROM. Tiene dos parámetros: el primero es la dirección de memoria (de 0 a 1023) donde se escribirá el byte; el segundo es el valor que se va a escribir en la EEPROM, que debe ser un valor entero entre 0 y 255. La función no retorna ningún valor.

Sintaxis:

      EEPROM.write(direccion,valor)

Parámetros:

direccion: dirección en la memoria (de 0 a 1023)
valor: el valor a escribir en la memoria

Ejemplo: Escribir valores en un sector de la memoria

FUNCIÓN EEPROM.update()

Escribe un byte en la EEPROM. El valor es escrito solo si es diferente al valor que esta previamente almacenado en esa posición de memoria.

Sintaxis:

      EEPROM.update(direccion,valor)

Parámetros:

direccion: la dirección de la memoria (0 a 1023)
valor: el valor a escribir en la memoria




FUNCIÓN EEPROM.put()

Esta función escribe cualquier tipo de dato en la EEPROM. El valor es escrito solo si es diferente al valor que esta previamente almacenado en esa posición de memoria, por lo que es mucho más versátil.

Sintaxis:

      EEPROM.put(direccion,valor)

Parámetros:

direccion: dirección en la memoria (0 a 1023)
valor: valor a escribir en la memoria

Ejemplo: Guardar una variable tipo float en la memoria EEPROM

FUNCIÓN EEPROM.get()

Permite leer cualquier tipo de dato en la EEPROM.

Sintaxis:

      EEPROM.get(direccion,variable)

Parámetros:

direccion: dirección en la memoria (0 a 1023)
variable: el nombre de la variable donde guardaremos el valor leído de la memoria

Ejemplo: Leer un valor float

OPERADOR EEPROM[]

Este operador permite usar el identificador EEPROM[] como una matriz en la cual indicamos la dirección.

Sintaxis:

      EEPROM[direccion]

Parámetros:

direccion: dirección en la memoria (0 a 1023)

Ejemplo: Uso del operador EEPROM[]