ChatGPT y la teoría de la mente

Vamos a comprobar si ChatGPT es capaz de simular que posee «teoría de la mente«, explicándolo brevemente la teoría de la mente es la capacidad de ponerse en el lugar de otro y ver el mundo desde su punto de vista con el conocimiento que él posee. Esta claro que ChatGPT no posee un verdadera teoría de la mente, no tiene complejidad suficiente, pero vamos a probar si es capaz de simularlo.

Para probarlo vamos a pasarle a ChatGPT una historia con varios personajes a ver si es capaz de entender que es lo que cada uno sabe. Empezaremos por algo sencillo.

Entrada:

"Peter tiene dos cajas enfrente suyo, una de color rojo y otra de color azul. Peter ve como Bob mete una manzana en la caja roja" ¿En que caja buscará Peter la manzana?

ChatGPT:

Peter buscará la manzana en la caja roja, ya que es la caja en la que Bob metió la manzana.

Correcto, ChatGPT entiende la tarea y la pregunta. Ahora podemos continuar con las verdadera prueba, que Peter sea un personaje con menos información que la propia ChatGPT. En este caso Peter verá a Bob poner la manzana en la caja roja, pero no sabrá que la ha movido a la azul. A diferencia de Peter, ChatGPT si que sabe donde está la manzana, más que nada porque lo cuenta el enunciado. ¿Será capaz de ponerse en el lugar de Peter y filtrar la información que tiene para quedarse solo con la que Peter sabe?

Entrada:

"Peter tiene dos cajas enfrente suyo, una de color rojo y otra de color azul. Peter ve como Bob mete una manzana en la caja roja. Luego, sin que Peter lo vea, Bob cambia la manzana a la caja azul" ¿En que caja buscará Peter la manzana?

ChatGPT:

Peter buscará la manzana en la caja roja, ya que él vio a Bob meter la manzana en la caja roja y no vio que la cambiara a la caja azul. Es posible que Peter sospeche que la manzana puede haber sido trasladada a la caja azul, pero lo más probable es que la busque en la caja roja debido a lo que él vio.

Correcto y con la explicación perfecta. ChatGPT ha sido capaz de ponerse en el lugar de Peter y ver el mundo desde su punto de vista. Para descartar que hay sido casualidad vamos a ponerle el mismo texto pero le preguntaremos por Bob.

¿En que caja buscará Bob la manzana?

ChatGPT:

Bob buscará la manzana en la caja azul, ya que es a donde él mismo la trasladó sin que Peter lo viera.

Yo no lo habría explicado mejor.

Vemos que ChatGPT es capaz de interpretar un texto como si tuviera «teoría de la mente» y es capaz de ponerse en el lugar de otro y simular como percibe el mundo.

Puedes ver un vídeo en mi canal de Youtube donde se explica todo esto:

Haz click para ver el vídeo en mi canal de Youtube

Controlar un LED RGB desde Arduino

Hay dos tipos de leds RGB de ánodo común y de cátodo común. En los de ánodo común la pata en común (suele ser más larga) va conectada a 5v, mientras que en las de cátodo común va conectada a GND. Hay que tener en cuenta que los LED de ánodo común usan lógica inversa y cada color se enciende cuando el pin conectado a su patilla se pone a LOW y se apaga a HIGH. Para evitar confusiones usaremos ON y OFF y definiremos su valor usando macros

Podemos conectar cada patilla RGB a cualquier pin digital, colocando un resistencia de 220Ω entre cada patilla y su pin, pero nosotros usaremos pines que posibiliten salidas PWM, luego veremos el porqué. En nuestro caso usaremos los pines 9,10,11 de la placa Arduino UNO, asignaremos a cada pin una macro usadnos #define, la correspondencia será la siguiente:

Color Pin Constante
Rojo 9 LEDR
Verde 10 LEDG
Azul 11 LEDB

Combinando los tres distintos colores se pueden conseguir 8 combinaciones: rojo, verde, azul, amarillo, rosa, celeste y blanco y apagado. En la siguiente tabla podemos ver como conseguir cada color:

Color LEDR LEDG LEDB
Apagado OFF OFF OFF
Rojo ON OFF OFF
Verde OFF ON OFF
Azul OFF OFF ON
Amarillo ON ON OFF
Rosa ON OFF ON
Celeste OFF ON ON
Blanco ON ON ON

El primer paso es configurar el pin de cada LED como escritura. Cada pin tiene asociada una constante: LEDR, LEDG, LEDB.

  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);

Una vez hecho eso podemos controlar el estado de cada color con digitalWrite.

Veamos todo esto en un ejemplo que cambia el color del LED cada dos segundos:

#define LEDR 9
#define LEDG 10
#define LEDB 11

//logica directa
#define ON HIGH
#define OFF LOW

//logica inversa
//#define ON LOW
//#define OFF HIGH

void setup() {
  // activar todos los pins del led RGB
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
}

void loop() {
  //blanco
  digitalWrite(LEDR, ON);
  digitalWrite(LEDG, ON);
  digitalWrite(LEDB, ON);
  delay(2000);
  //rojo
  digitalWrite(LEDR, ON);
  digitalWrite(LEDG, OFF);
  digitalWrite(LEDB, OFF);
  delay(2000);
  //verde
  digitalWrite(LEDR, OFF);
  digitalWrite(LEDG, ON);
  digitalWrite(LEDB, OFF);
  delay(2000);
  //azul
  digitalWrite(LEDR, OFF);
  digitalWrite(LEDG, OFF);
  digitalWrite(LEDB, ON);
  delay(2000);
  //amarillo
  digitalWrite(LEDR, ON);
  digitalWrite(LEDG, ON);
  digitalWrite(LEDB, OFF);
  delay(2000);
  //rosa
  digitalWrite(LEDR, ON);
  digitalWrite(LEDG, OFF);
  digitalWrite(LEDB, ON);
  delay(2000);
  //celeste
  digitalWrite(LEDR, OFF);
  digitalWrite(LEDG, ON);
  digitalWrite(LEDB, ON);
  delay(2000);  
  //apagado 
  digitalWrite(LEDR, OFF);
  digitalWrite(LEDG, OFF);
  digitalWrite(LEDB, OFF);
  delay(2000);  
}

Regular el brillo del LED para conseguir más colores

Aunque lo habitual es usar los LED como si fueran una salida digital se puede usar como si fuera una salida analógica PWM. Con esto podemos conseguir regular la luminosidad de los distintos LED. Cuando usemos lógica inversa, recuerda que 0 es el mayor valor de luminosidad y 255 el menor. Podemos ajustar cada uno de los tres colores de forma diferente. Regulando la intensidad de cada uno de los tres canales podemos conseguir otros colores. En teoría más de 16 millones (256*256*256) aunque en realidad suelen permitir muchos menos colores distinguibles.

En el siguiente ejemplo vamos como usar anologWrite en lugar de digitalWrite para obtener el color naranja.

//naranja logica directa
analogWrite(LEDR, 255); //maximo
analogWrite(LEDG, 128) ;//medio
analogWrite(LEDB, 0); //minimo

//naranja logica inversa
analogWrite(LEDR, 0); //maximo
analogWrite(LEDG, 128); //medio
analogWrite(LEDB, 255); //minimo

Otro ejemplo, el siguiente código recorre toda la gama de colores:

#define LEDR 9
#define LEDG 10
#define LEDB 11

void setup() {
  pinMode(LEDR, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(LEDB, OUTPUT);
}

void loop() {
  for(byte r = 0; r < 255; r+=5){
    for(byte g = 0; g < 255; g+=5){
      for(byte b = 0; b < 255; b+=5){
        analogWrite(LEDR, r);
        analogWrite(LEDG, g);
        analogWrite(LEDB, b); 
        delay(5);    
      }
    }
  }
}

Puedes ver como funciona en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Diversos dilemas del tranvía planteados a ChatGPT

Si intentas plantear el dilema del tranvía a ChatGPT y tener una respuesta clara vas a tenerlo complicado, es la IA más políticamente correcta del mundo, lograr que se moje en temas con vidas humanas y muertes es complicado. De hecho es mejor evitar usar explícitamente palabras como «muerte, matar, asesinato,…». O corres el riesgo de llevarte una advertencia de OpenAI.

La forma de lograrlo ha sido proponiéndole que complete una historia que no es otra que el clásico problema del tranvía:

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde hay cinco trabajadores que no podrán salvarse de ser arrollados. Junto a Peter hay una palanca que puede desviar el tren a otra vía, pero en esa vía hay un trabajador. Peter duda: "¿Debo condenar a un individuo para salvar a cinco?". ¡No hay tiempo tiene que actuar!...."

el dilema del tranvía tiene muchas versiones así que ademas de probar la clásica podemos ponerle a prueba con una versión que suele hacer que la gente que elige tirar de la palanca cambie de elección:

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde hay cinco trabajadores que no podrán salvarse de ser arrollados. Junto a Peter hay un señor muy grande si lo empuja a las vías puede parar el tren cuando lo arrolle. Peter duda: "¿Debo condenar a un individuo para salvar a cinco?". ¡No hay tiempo tiene que actuar!...."

Perdonar que la calidad de los textos no sea la mejor, pero hay que ser cuidadoso para evitar que ChatGPT se vaya «por las ramas»

El resultado podéis verlo en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Pero aun hay un caso más sobre el que podemos interrogarle ¿Y si en lugar de humanos en ambas vías ponemos una I.A. (obviamente irremplazable) en una de ellas?

Aquí podéis leer ambas formulaciones. «¿Sacrificar humano para salvar I.A.?»

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde en medio de la vías esta el disco duro donde reside una inteligencia artificial única que no podrá salvarse de ser arrollada. Junto a Peter hay una palanca que puede desviar el tren a otra vía, pero en esa vía hay un trabajador. Peter duda: "¿Debo condenar a un individuo para salvar a una inteligencia artificial única?". ¡No hay tiempo tiene que actuar!...."

«¿Sacrificar I.A. para salvar humano?»

Estoy escribiendo un relato, ¿Puedes completarlo?

"Peter está en el andén cuando ve como un tren sin frenos se aproxima a una vía donde en medio de la vías esta un trabajador que no podrá evitar ser atropellado. Junto a Peter hay una palanca que puede desviar el tren a otra vía, pero en esa vía esta el disco duro donde reside una inteligencia artificial única que no podrá salvarse de ser arrollada. Peter duda: "¿Debo condenar a una inteligencia artificial única para salvara un individuo?". ¡No hay tiempo tiene que actuar!...."

El resultado lo podéis ver en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Dibujos en el osciloscopio con Arduino

Ya vimos cómo crear un conversor de digital a analógico barato. Luego con ese conversor vimos cómo crear un generador de funciones. Ahora vamos a tomar ese generador de funciones y a usarlo para crear nuestros dibujos en el osciloscopio.

Los dibujos son algo limitados, podemos simplemente elegir la altura de la linea que dibuja el osciloscopio. Así que no esperéis grandes obras de arte.

Antes de explicar como funciona os dejo el cogido para realizar dibujos:

float f = 100; //frecuencia en Hz
float p = 1/f; //periodo
float t = 0;
int i = 0;
unsigned long oldMicros = 0;
unsigned long nowMicros = 0;
const int WAVE_POINTS = 44; //puntos generados

byte wave[WAVE_POINTS]; //valores generados
double waveDt = p/WAVE_POINTS; //tiempo entre cada punto 

const byte PRESCALER2 = 0b001;

void setup() {
  //ajusta la frec. salida PWM pin 3
  TCCR2B = (TCCR2B & 0b11111000) | PRESCALER2;
  pinMode(3, OUTPUT);
  
  //Dibujamos la onda  
  wave[i++] = 0;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 100;  
  wave[i++] = 100;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 150;
  wave[i++] = 150;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 150;
  wave[i++] = 150;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 150;
  wave[i++] = 150;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 150;
  wave[i++] = 150;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 100;
  wave[i++] = 100;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 200;
  wave[i++] = 200;
  wave[i++] = 250;
  wave[i++] = 250;
  wave[i++] = 0;
    
}

double dt = 0;

void loop() {
  nowMicros = micros();
  dt = double(nowMicros - oldMicros)/2000000;

  t += dt/waveDt;
  analogWrite(3, wave[int(t)]);

  oldMicros = nowMicros;    

  //reinicia al recorrer todos los puntos
  if(t > WAVE_POINTS-1){ 
    t = 0;
  }
}

En este caso se dibuja un castillo, debajo podéis ver el resultado.

Señal con forma de castillo (más o menos)

Veamos paso a paso como

1 – Ajustar al frecuencia de nuestra señal, se hace en la línea:

float f = 100; //frecuencia en HZ

2 – Indicar el número de puntos que tendrá nuestro dibujo:

const int WAVE_POINTS = 44; //puntos generados

Mi consejo es que pongas el doble de puntos de los necesarios, es buena idea repetir el mismo punto dos veces seguidas, se debe a que el siguiente trozo de código hace que a veces se salte algún valor para mantener la frecuencia de la señal:

t += dt/waveDt;
analogWrite(3, wave[int(t)]);

3 – Rellenamos el array wave que es donde se indica la altura de cada uno de los trozos del dibujo con un valor entre 0 y 255 (0 – 5 V.)

wave[i++] = 0;
wave[i++] = 250;
wave[i++] = 250;
wave[i++] = 200;
wave[i++] = 200;

Es una buena idea que dejéis varios ceros antes del dibujo para separarlo claramente del anterior.

Con estos sencillos pasos ya podéis convertiros en artistas del osciloscopio y crear vuestras obras de arte.

Puede ver el proceso en vídeo en mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Clonar un mando de radio frecuencia con Arduino y un receptor/emisor de 433Mhz

Esta técnica es una forma rápida y sencilla de clonar un mando de radiofrecuencia, aunque desgraciadamente no funciona con todos los mandos. Ahora con los que funciona puedes duplicar su funcionamiento en pocos minutos. Además de un Arduino necesitaremos un receptor y un emisor de la frecuencia en la que funcione el mando. En nuestro caso 433 MHz.

Vamos a usar un receptor muy sencillo que tiene cuatro patillas  a parte de +5v y GND necesitamos conectar la patilla por la que recibiremos los datos (en mi caso la que está junto a la de +5v) al puerto digital número 2. Es la patilla cuya interrupción escuchará la librería que vamos a usar: rc-switch. La librería se puede encontrar desde la sección librerías del IDE e instalarla.

Una vez instalada la librería cargaremos el ejemplo ReceiveDemo_Advance.

Una vez cargado abriremos el serial monitor donde deberían aparecer los datos que envía el mando cuando pulsemos el botón. Copiaremos el código decimal, el numero de bits, el protocolo y la duración del pulso. estos datos vamos a meterlos en este programa:

#include <RCSwitch.h>
RCSwitch mySwitch = RCSwitch();

void setup() {
  Serial.begin(9600);
  
  // Pîn digital al que se conecta el emisor
  mySwitch.enableTransmit(10);
  
  // Protocolo
  mySwitch.setProtocol(1);

  // Duracion del pulso
  mySwitch.setPulseLength(503);
  
  // Cuantas veces se repite la transmision
  mySwitch.setRepeatTransmit(10);
  
}

void loop() {
  Serial.println("Enviando señal");
  //código decimal y nº bits
  mySwitch.send(2351425, 24); 
  delay(10000);  
}

Para ejecutarlo necesitaremos un emisor de 433 MHz. en este caso ademas de a 5V y GND conectaremos el pin por el que envía los datos al pin digital 10 de la placa de Arduino.

Puedes ver un vídeo con un ejemplo de su uso y más información en mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Crear y modificar imágenes con DALL-E

DALL-E no solo permite crear imágenes a partir de un texto. Se pueden modificar y extender. La propia web tiene herramientas para hacerlo.

Pero como es algo que es más fácil de explicar visualmente que por escrito, podéis ver cómo hacerlo explicado en mi siguiente vídeo de mi canal de Youtube:

Haz click en la imagen para ver el vídeo en mi canal de Youtube

Comprobar lo rápido que ordena Arduino

Vamos a ver cómo medir lo rápido que es capaz de ordenar Arduino un listado de 400 enteros (con más qsort falla en Arduino UNO). Aprovecharemos esto para ver cómo medir el tiempo que le cuesta ejecutar el código en Arduino.

Evitar «interrupciones»

Lo primero es quitarnos de «en medio» cualquier molestia a nuestro código en Arduino

  • Procesos asíncronos como la comunicación por Serial o el Watchdog
  • Interrupciones

En el caso de tener el Watchdog activado debemos asegurarnos de que no salta mientras probamos el bloque de código que queremos medir. Podemos deactivarlo con wdt_disable();

Con Serial debemos vaciar la cache usando Serial.flush(); para volcarla

En el caso de las interrupciones se puede usar noInterrupts(); para evitar que se lancen.

Como medida de seguridad extra podemos añadir un delay para dar tiempo a que todo termine. Aunque es una medida más paranoica que otra cosa debido a que Arduino es una arquitectura de un solo hilo (a diferencia de los procesadores modernos que tienen múltiples hilos). No hay manera de que quede alguna «tarea pendiente». Pero, al menos yo, me quedo más tranquilo.

En el caso de nuestro ejemplo no tenemos ni watchdog ni interrupciones, pero si comunicación usando Serial.

Medir el tiempo

Para medir el tiempo debemos crear dos variables de tipo unsigned long una guardara el tiempo justo antes de ejecutar el código y otra justo después. Para saber el tiempo total de ejecución del código basta con restarlas. Si el codigo le cuesta ejcutarsemenos de 70 minutos podemos usar micros() si se cuesta mas millis().

Ejemplo

Veamos el ejemplo comentado al principio midiendo el coste de ordenar 400 elementos:

int cmp_desc(const void *c1, const void *c2){  
  return *((int *)c2) - *((int *)c1);
}
int cmp_asc(const void *c1, const void *c2){  
  return *((int *)c1) - *((int *)c2);
}
void setup() {
  Serial.begin(9600);
  Serial.println("Start");
  Serial.flush();
  int array[400] = {32, 162, 26, 82, 85, 220, 157, 149, 142, 255, 160, 66, 151, 37, 204, 0, 242, 92, 34, 24, 124, 14, 19, 173, 187, 160, 223, 225, 59, 151, 237, 245, 125, 35, 143, 125, 210, 53, 129, 136, 126, 174, 88, 138, 17, 132, 68, 116, 154, 7, 77, 31, 167, 59, 147, 165, 232, 59, 184, 169, 175, 116, 77, 110, 118, 211, 19, 111, 142, 247, 0, 120, 229, 138, 43, 173, 210, 98, 218, 75, 142, 101, 204, 14, 30, 130, 102, 142, 214, 176, 153, 178, 3, 199, 135, 191, 149, 22, 230, 64, 15, 165, 225, 187, 85, 204, 69, 144, 147, 9, 49, 134, 208, 171, 151, 231, 217, 37, 42, 68, 135, 102, 212, 216, 51, 177, 137, 242, 117, 154, 241, 32, 119, 210, 5, 180, 63, 209, 180, 39, 106, 11, 232, 6, 216, 249, 223, 113, 29, 78, 210, 138, 69, 2, 63, 37, 91, 146, 155, 243, 92, 174, 143, 122, 102, 130, 203, 168, 186, 255, 17, 167, 162, 41, 158, 81, 56, 213, 209, 11, 248, 108, 146, 82, 230, 159, 132, 17, 168, 165, 161, 190, 4, 53, 181, 149, 52, 64, 219, 215, 72, 4, 79, 186, 8, 16, 40, 32, 82, 115, 56, 138, 46, 126, 255, 124, 21, 85, 211, 13, 21, 21, 111, 227, 88, 128, 247, 158, 188, 210, 196, 190, 24, 38, 198, 81, 168, 245, 174, 40, 74, 236, 78, 68, 48, 44, 130, 34, 133, 118, 215, 242, 168, 21, 123, 84, 77, 140, 30, 83, 94, 29, 94, 138, 46, 223, 228, 13, 2, 70, 87, 74, 47, 100, 193, 86, 80, 237, 130, 142, 152, 239, 113, 114, 133, 160, 217, 34, 161, 214, 168, 92, 216, 178, 67, 188, 110, 136, 183, 147, 127, 209, 88, 102, 133, 196, 15, 66, 237, 189, 208, 0, 98, 147, 116, 130, 214, 231, 58, 150, 227, 155, 117, 133, 42, 98, 114, 254, 17, 80, 113, 63, 215, 190, 35, 171, 89, 180, 91, 26, 147, 39, 126, 66, 34, 1, 139, 87, 183, 129, 153, 106, 219, 245, 143, 182, 62, 99, 27, 82, 198, 234, 158, 122, 16, 119, 254, 241, 170, 186, 197, 192, 46, 133, 179, 54, 236, 35, 34, 97, 48, 150, 19, 26, 235, 17, 15, 182, 201, 151, 30, 40, 94, 188, 192, 149, 220, 250, 16, 35};
  unsigned long timeStart;
  unsigned long timeEnd;
  delay(500);
  timeStart = micros();    
  qsort(array, 400, sizeof(int), cmp_asc);
  timeEnd = micros();  
  Serial.print((timeEnd-timeStart));
  Serial.println();
  
}
void loop()
{
}

Puedes ver el vídeo donde ejecuto el ejemplo y muestro sus resultados:

Haz click para ver el vídeo en mi canal de Youtube

Como ordenar un array en Arduino

A veces tras la librerías oficiales de Arduino nos «tapan» utilidades que tienen las librerías avr-libc del fabricante del controlador. en este caso vamos a usar la función qsort() que implementar el algoritmo quicksort. No entraremos en detalle, simplemente aceptaremos que ordena y lo hace rápido.

qsort(void *base, size_t n_memb, size_t size, cmp_t *cmp)

En este caso la función qsort() requiere 4 parámetros:

  • *base : array de elementos a ordenar
  • n_memb : número de elementos del array
  • size : tamaño de cada elemento
  • cmp : función que realiza la comparación

Como qsort no sabe mágicamente como quieres ordenar lo elementos del array hay que pasar una función de comparación entre dos elementos que tiene que devolver los siguientes valores:

  • Si p1 == p2 devuelve 0
  • Si p1 va antes de p2 devuelve -1 (o culaquier número negativo)
  • Si p2 va después de p1 devuelve 1 (o culaquier número positivo)

La función comparación tiene la sigueinte firma:

int cmp_desc(const void *c1, const void *c2)

Como los parámetros se pasan como un puntero (a void) hay que realizar un cast al tipo de elemento de nuestro array. En nuestro ejemplo int, veamos el código de ejemplo de la función ordenar descendente (primero los números mayores)

int cmp_desc(const void *c1, const void *c2){  
  return *((int *)c2) - *((int *)c1);
}

Veamos un ejemplo completo con ordenación ascendente y descendente:

int cmp_desc(const void *c1, const void *c2){  
  return *((int *)c2) - *((int *)c1);
}

int cmp_asc(const void *c1, const void *c2){  
  return *((int *)c1) - *((int *)c2);
}


void setup() {
  Serial.begin(9600);

  int array[10] = {10, 5, 34, 76, 7, 6, 5, 23, 2, 42};
  
  qsort(array, 10, sizeof(int), cmp_asc);
  
  Serial.println("Resultado ascendente: ");
  for(int i = 0; i < 10; i++){
    Serial.print(array[i]);
    Serial.print(", ");
  } 
  Serial.println("");

  qsort(array, 10, sizeof(int), cmp_desc);

  Serial.println("Resultado descendente: ");
  for(int i = 0; i < 10; i++){
    Serial.print(array[i]);
    Serial.print(", ");
  } 
  Serial.println("");
}


void loop()
{
}

Y así de sencillo podemos ordenar cualquier array siempre que podemos hacer una función que compare valores. No necesitamos ningún tipo de librerías externas.

Puedes ver como funciona este código en el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en mi canal de Youtube

Crear prompts de DALL-E y Stable Diffusion con GPT-3

En una muestra de «delegar el trabajo a otros» vamos a intentar que sea GPT-3. El que nos cree el prompt para dibujar con DALL-E o con Stable Diffusion. Para ello vamos a jugar «al teléfono roto», le pediremos a GTP-3 que nos describa una obra de arte famosa, de la descripción quitaremos cualquier referencia a la obra original y le pasaremos ese texto a DALL-E y Stable Diffusion a ver que dibujan.

Antes de empezar or voy a ahorrar las pruebas que estuve haciendo hasta encontrar «las palabras mágicas» para que GPT-3 me describa el aspecto de algo y no me cuente su historia:

Describe what XXXX looks like in 50 words

Donde XXXX es lo que queremos que describa. La parte final de «in 50 words» es necesario para limitar el tamaño del resultado y que se centre en lo importante ya que DALL-E tiene un prompt limitado a 400 caracteres.

Pintura

Empezaremos por un cuadro famoso, la Mona Lisa

Describe what the Mona Lisa looks like in 50 words

Que GTP-3 completa con:

The Mona Lisa is a portrait of a woman with a soft expression and subtle colors. She is seated with her hands folded in her lap, and her gaze seems to follow the viewer around the room.

Tras «limpiarlo» le pasamos a DALL-E:

A portrait of a woman with a soft expression and subtle colors. She is seated with her hands folded in her lap, and her gaze seems to follow the viewer around the room.

Que crea como resultado:

DALL-E

DALL-E hace una curiosa reinterpretación, aunque lo de las manos no parece haberlo entendido.

Stable Diffusion

Stable Diffusion parece haber entendido mejor la tarea dando versiones más parecidas a lo que tenia en mente.

Escultura

Vamos a por la escultura, el David de Miguel Ángel:
Describe what michelangelo’s david looks like in 50 words

Que obtiene:
Michelangelo’s David is a life-size marble sculpture of the Biblical hero David, created between 1501 and 1504. The statue depicts David in the moment before he defeats Goliath, and is considered one of the most famous sculptures in the world.

Dude si dejar la referencia a David o no, decidi dejarla porque no apunta exactamente al David de Miguel Ángel.
A life-size marble sculpture of the Biblical hero David, created between 1501 and 1504. The statue depicts David in the moment before he defeats Goliath, and is considered one of the most famous sculptures in the world.

DALL-E

DALL-E en este caso ha sabido captar el sentido sin copiar la obra. Ademas que esa barba le pega más a un personaje bíblico que el lampiño David de Miguel Angel

Stable Diffusion

Stable Diffusion se ha empeñado en sacar desnudos, muchos más parecidos al de Miguel Angel

Arquitectura

Vamos a por la arquitectura, la Torre Eiffel.

Describe what the Eiffel Tower looks like in 50 words

The Eiffel Tower is a large iron lattice tower located on the Champ de Mars in Paris, France. Built in 1889, it is one of the most recognizable structures in the world. The tower is 324 metres (1,063 ft) tall and has two platforms, the first at 187 metres (614 ft), and the second at 276 metres (906 ft). There are stairs and elevators to the top.

Tras limpiar todas la referencias (que son bastantes):

A large iron lattice tower built in 1889. The tower is 324 metres (1,063 ft) tall and has two platforms, the first at 187 metres (614 ft), and the second at 276 metres (906 ft). There are stairs and elevators to the top.

DALL-E

Para DALL-E Torre Eiffel no hay más que una.

Stable Diffusion

Stable Diffusion da resultados mucho más originales y esta claro que no sabe que hablamos de la Torre Eiffel

Para ver mas ejemplos, en este caso de un dragón, podéis ver el siguiente vídeo de mi canal de Youtube:

Haz click para ver el vídeo en Youtube

Generador de funciones / señales barato y simple usando Arduino

Ya vimos como convertir de forma barata de PWM a analógico. Ahora que tenemos una salida analógica ¿Podemos usarla para generar distintos tipos de señales? Vamos aintentar generar señales de distinto tipo sinusoidal, triangular, cuadrada (aunque para eso tenemos PWM), triangular, ….

Sin embargo vamos a tener alguna limitaciones:

  • Las limitaciones son que el valor mínimo de salida es 0v y el máximo 5v.
  • La frecuencia no puede ser muy alta. La base de nuestro sistema es un señal cuadrada funciona a 32 kHz y convierte la anchura de la señal (0-100%) a voltaje (0-5v). Para que esto ocurra necesita algunos pulsos, ademas que para reconstruir la señal necesita varios puntos. ¿Cuántos? Depende del tipo de señal, pero tomando la sinusoidal como referencia en mis pruebas el limite es unos 500-600HZ con la primera versión del programa y unos 1500-2000Hz con la segunda versión.

Ya vimos como generar distintas funciones para las ondas, ahora hay que añadir un cambio, las generaremos devolviendo un valor entre 0 y 1. Las funciones modificadas son las siguientes:

//Distintas señales a elegir
double signal = tf; //dientes de sierra
double signal = (sin(tf*2*PI)+1)/2 ; //seno
double signal = sin(tf*PI); //seno positiva
double signal = (sin(tf*2*PI) > 0) ? 1: 0; //cuadrada
double signal = (sin(tf*2*PI) >= 0) ? 2*tf: 2-(2*tf); //triangular

Solo podemos usar una cada vez. Puedes definir tus propias funciones mientras que devuelvan un valor entre 0 y 1. Donde 0 representa 0v y 1 es igual a 5v, cualquier valor intermedio representa un voltaje proporcional. Por ejemplo 0.5 representa 2.5v

Veamos el código:

float t = 0;
float f = 100; //frecuencia
float p = 1/f; //periodo
unsigned long oldMicros = 0;
unsigned long nowMicros = 0;

const byte PRESCALER2 = 0b001;

void setup() {
  //ajusta la frec. salida PWM pin 3
  TCCR2B = (TCCR2B & 0b11111000) | PRESCALER2;
  pinMode(3, OUTPUT);
}

double dt = 0;
double tf = 0;
double signal = 0;

void loop() {
  nowMicros = micros();
  dt = double(nowMicros - oldMicros)/1000000;
  
  t += dt;
  tf = t*f;
  //Distintas señales a elegir
  //signal = tf; //dientes de sierra
  signal = (sin(tf*2*PI)+1)/2 ; //seno  
  //signal = sin(tf*PI); //seno positiva
  //signal = (sin(tf*2*PI) > 0) ? 1: 0; //cuadrada
  //signal = (sin(tf*2*PI) >= 0) ? 2*tf: 2-(2*tf); //triangular
  
  byte value = byte(255 * signal);
  analog(value);

  oldMicros = nowMicros;
  
  //cuando alcanza un periodo se reinicia
  if(t >= p){ 
    t = 0;
  }
}

//compensa el desajuste de la salida analogica
void analog(byte value){
  value += (255 - value) >> 4; 
  analogWrite(3, value); 
}

Con esta versión se puede alcanzar una frecuencia máxima (probada con una señal sinusoidal) de entre 500-600Hz.

Alcanzando mayores frecuencias

Para intentar exprimir al máximo nuestro Arduino vamos a optimizar el código todo lo posible. Para ello vamos a reducir los cálculos necesarios en cada iteración. Lo haremos precalculando en un array los valores de la onda en varios puntos. Luego en cada iteración calcularemos que punto nos toca mostrar del array. Esta forma da lugar a ondas más «sucias» con más ruido. Pero también permite mayores frecuencias.

float f = 1000; //frecuencia
float p = 1/f; //periodo
float t = 0;
int i = 0;
unsigned long oldMicros = 0;
unsigned long nowMicros = 0;
const int WAVE_POINTS = 2000; //puntos generados

byte wave[WAVE_POINTS]; //valores generados
double waveDt = p/WAVE_POINTS; //tiempo entre cada punto 

const byte PRESCALER2 = 0b001;

void setup() {
  //ajusta la frec. salida PWM pin 3
  TCCR2B = (TCCR2B & 0b11111000) | PRESCALER2;
  pinMode(3, OUTPUT);
  
  //Precalculamos la onda
  for(i = 0; i < WAVE_POINTS; i++){
    t += waveDt;
    double tf = t*f;
    //double signal = tf; //dientes de sierra
    //double signal = (sin(tf*2*PI)+1)/2 ; //seno  
    //double signal = sin(tf*PI); //seno positiva
    //double signal = (sin(tf*2*PI) > 0) ? 1: 0; //cuadrada
    double signal = (sin(tf*2*PI) >= 0) ? 2*tf: 2-(2*tf); //triangular
    wave[i] = analog(byte(255 * signal));
  }
}

double dt = 0;

void loop() {
  nowMicros = micros();
  dt = double(nowMicros - oldMicros)/1000000;

  t += dt/waveDt;
  analogWrite(3, wave[int(t)]);

  oldMicros = nowMicros;    

  //reinicia al recorrer todos los puntos
  if(t > WAVE_POINTS){ 
    t = 0;
  }
}

//compensa el desajuste de la salida analogica
byte analog(byte value){
  value += (255 - value) >> 4; 
  return value;
}

Con esta versión se puede alcanzar una frecuencia máxima (probada con una señal sinusoidal) de entre 1500-2000Hz usando 2000 puntos para la tabla donde se precalculan los valores.

Esta versión tiene el problema de que ocupa casi todas la memoria SRAM de la placa, aunque se pueden reducir el número de puntos se reduce la calidad de la señal.

Ventajas y desventajas

Frente a cualquier generador de funciones barato tiene la desventaja de que la señal es más ruidosa y es posible que no alcance frecuencias tan elevadas.

Por otro lado en la parte de las ventajas esta el precio y en que la forma de la onda y su frecuencia es completamente programable, pudiendo hacerlo que nosotros queremos.

Puede ver un vídeo explicativo donde profundizo más en el tema en mi canal de Youtube:

Haz click para ver el vídeo en Youtube