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

Autómatas celulares y evolución

Vamos a usar los autómatas celulares para modelar una versión muy simple de evolución y luego jugar con ello.

Para simular la evolución necesitamos «algo» en cada individuo (celda) que haga la función de código genético que será sometido a un proceso de selección competitiva según lo adaptado que este al entorno. Los más adaptados tendran más oportunidades de replicarse, pero durante la misma se verán sometidos al cruce con el código genético de otro individuo y a mutaciones.

Veamos en detalle cada una de estas parte.

Genes, genotipo y fenotipo

Vamos a empezar a describir los genes de nuestro individuo. En nuestro caso cada gen es un único bit, por lo que puede tener 2 valores 0 y 1. Cada individuo tiene 8 genes por lo que entre todos serán un único byte. Hay 256 posibles combinaciones de genes (genotipos). Para representar el aspecto (fenotipo) de cada individuo usaremos el nivel de gris representado por el valor del byte que representa sus genes. Este color ira desde negro para el valor mínimo (0) a blanco para el valor máximo (255), pasando por distintos tonos de grises.

Función fitness

Es la función que devuelve un valor que puntúa lo bien adaptado que está un individuo al entorno. Tenemos todo un artículo dedicado a ella.

En este caso tendremos dos funciones fitness para poder jugar con ellas. Para ambas tomaremos el valor del bit representado por los genes. Por ejemplo, el genotipo 00000010 valdrá 2 y el genotipo 00000110 valdrá 6. Esto hace que no todos los genes tengan la misma importancia. También jugaremos con eso.

Para lo que no tengan claro como funcionan los numeros binarios pueden mirar la siguiente tabla para saber cuanto vale cada gen (el valor total del genotipo seria la suma de los valores de todos los bits que estén a 1)

Nº de bit76543210
Valor1286432168421

Hemos dicho que habrá dos funciones fitness una considera como más aptos a los individuos con mayor valor en sus genes. La otra a los individuos con menor valor. Ya veremos cómo jugamos con esto.

En la interfaz está la opción de cambiar la función fitness. Hay tres posibilidades: higth, que da preferencia a los bits de mayor valor; los, que da preferencia a los de menor valor y two, que usa ambas funciones fitness

Selección

Para que haya evolución necesitamos un proceso de selección, en cada iteración cada uno de nuestros individuos elegirá a uno de sus vecinos para cruzarse con él. La selección se hará con un mecanismo de ruleta, es decir, al azar, pero cada vecino tiene diferente probabilidad de salir elegido según el resultado de la función fitness. Los más adaptados son más probables.

Cruce

Una vez seleccionado el otro progenitor, se realiza el cruce de sus genes. El proceso consiste en tomar los 4 bits superiores del individuo, los 4 bits inferiores del vecino y con unirlos para formar el genotipo de descendiente. Esto creará un individuo con un código genético nuevo, mezcla de ambos individuos, que ocupará el lugar de su progenitor.

Mutación

Añadiremos mutaciones, cada cruce tiene cierta probabilidad de sufrir una mutación en uno de sus genes. Al producirse una mutación se cambia el valor de ese gen. Si es 0 pasa a ser 1 y viceversa.

La interfaz permite elegir la probabilidad de que está mutación ocurra en cada cruce. Se puede ajustar su valor entre 0 y 5% aunque lo recomendable es en 0,01% y 0,05%.

Creando el mundo

Cada uno de los individuos del mundo se crean con un valor entre 0 y 127. Esto se hace así para que haya un gen, el de mayor peso (gen 7), que este a 0 en todos ellos. La idea es que ese gen solo se puede poner a 1 a través de una mutación. Visualmente significa que solo puede haber cuadrados entre negro y gris claro. Para que haya cuadrados blancos tiene que producirse una mutación.

Jugando con todo esto

Empecemos por un punto importante, hemos dicho que, al principio, ningún individuo tendrá el gen 7 a 1. De tal manera que si tenemos individuos blancos será gracias a una mutación. Así que si iniciamos el autómata como mutaciones a 0 y fitness a «Hight» veremos que no es capaz de alcanzarlo, si añadimos unas pocas mutaciones lo alcanza sin problemas.

Gen de mayor peso a 0: Con mutaciones – Sin mutaciones

Ahora teniendo todo en blanco y con mutaciones vamos a cambiar la función fitness a «Low» vemos que cambia a negro, los individuos han sido capaces de adaptarse al entorno. Probemos los mismo, partiendo del estado con la mayoría de los individuos blancos, pero con las mutaciones a 0, el resultado es que nada cambia. Sin mutaciones y una población altamente especializada no hay variedad genética como par adaptarse al entorno.

De Hight a Low: Sin mutaciones – Con mutaciones

Veamos otro caso distinto probemos fitness a «Two» que indica que hay una función fitness distinta para cada zona del tablero. Al ejecutarlo veremos un fenómeno que se llama «especiación» en el cual a partir de una especie se crean dos separadas genéticamente.

Especiación

Ya hemos dicho antes que la incapacidad de adaptarse a un entorno es por la falta de variedad genética, al estar en un entorno dominado por muy poca variedad de genes y suprimir la mutaciones como fuente de variación la especie queda atrapada y no puede adaptarse. Pero en el caso anterior tenemos el tablero dividido en dos, hay variedad, aunque suprimamos las mutaciones debería de poder adaptarse y así es. Es capaz de adaptarse a cualquier cambio en el entorno, pero solo una vez.

¿Qué pasa si nos vamos al lado contrario aumentamos mucho las mutaciones?. Que vemos que los ganes varían tanto y tan rápido que es imposible mantener la población estable. Pensad que el simulador solo deja poner una tasa de mutación del 5% (1 de cada 20 individuos tiene una mutación) que parece una tasa baja y aun así se puede ver visualmente como afecta e impide que la población se adapte completamente al entorno.

Podéis probar vosotros mismos aquí y si queréis echar un vistazo al código fuente podéis hacerlo aqui.

Puedes ver un vídeo con más explicaciones en mi canal de Youtube:

Haz click para ver mi vídeo en Youtube

Stable Diffusion se enfrenta a un examen de dibujo.

Ya hemos visto a DALL-E enfrentarse a varios retos. Hoy enfrentamos a otro modelo, está vez código libre, Stable Diffusion. Le he pedido a un profesor de dibujo que le ponga un par de ejercicios y valoremos el resultado.

Ejercicio 1: Mándala

El enunciado del ejercicio es:

Crea un zendala. Elige una gama cromática de colores fríos o cálidos y crea una armonía cromática con colores afines. Usa un mínimo de dos colores y un máximo de tres.

Cambiamos «crear» por «dibuja» y «zendala» por «mandala», palabra mucho más conocida. Lo traducimos al inglés:

Draw a mandala. Choose a chromatic range of cold or warm colors and create a chromatic harmony with similar colors. Use a minimum of two colors and a maximum of three.

Y el resultado es:

Varios puntos a tener en cuenta:

  • Simula ser un dibujo a mano, coloreado y es un mandala. Lo damos por válido.
  • Escala cromática. Falla, no es lo que le hemos pedido, lo intenta pero no sabe que hacer. Incorrecto.

Ejercicio 2: Camaleón puntillista

El siguiente ejercicio tiene dos partes  (iba a ser solo una pero el profesor sugirió un cambio)

Dibuja un camaleón con puntillismo en una jungla

Nos encontramos con la duda de ¿Cuál es la traducción de puntillismo? Google sugirió «pointillism» no estábamos seguros pero continuamos. El texto quedó:

draw a chameleon with pointillism in a jungle
  • Lo esperábamos en blanco y negro, pero no sé lo especificamos. El profesor da por bueno el resultado.
  • Solo es puntillista el camaleón, no la jungla. Puede ser culpa nuestra por no indicarlo ya que puntillismo hace referencia al camaleón
  • Falta un «trozo» de camaleón. Algo habitual en este tipo de IA, no se les da muy bien encuadrar la imagen

La pega es que el profesor esperaba un dibujo en blanco y negro, por lo que había que especificarlo en el prompt. Además pidió otro cambio. No se fiaba mucho de que el algoritmo no estuviera «copiando» imágenes. Por lo que en lugar de en la jungla, algo típico para un camaleón, pidió algo diferente, una escalera.

Cómo cuando usamos «stairs» dibujo un camaleón con un lápiz en la mano ¿? Usamos la palabra «ladder».

Dibuja un camaleón con puntillismo bajando una escalera. Blanco y negro
draw a chameleon with pointillism going down a ladder. Black and white
  • El profesor le da el visto bueno y lo considera como superado
  • Yo señalo que no esta «bajando la escalera». Pero no parece tener importancia

Resultado:

No es fácil dar una nota numérica ya que por un lado los dibujos son espectaculares, por otro comete errores muy simples como el usar más de tres colores en el mandala o cortar al figura del camaleón. El resultado seria claramente más que aprobado aunque no llegaría a sobresaliente.

Puedes ver la explicación en vídeo en mi canal de Youtube:

Haz click para ver el vídeo en Youtube

Usar Serial Plotter con Arduino

Todos los que usamos el editor de Arduino estamos acostumbrados al Monitor Serie o Serial Monitor para ver los datos que se envían por el puerto serie del Arduino. Pero junto a él, en el menú «Tools», hay otra herramientas que permite ver estos datos forma visual el Serial Plotter que dibuja una gráfica (o varias) con los datos que le devuelva el puerto serie.

Su uso es muy sencillo los datos tiene que seguir ir codificados de la siguiente manera:

  • En cada linea van los datos separados por coma y espacio «, «
  • Cada nueva linea va separada de la anterior por un salto de linea

La forma mas sencilla de entenderlo es con este código:

Serial.print(var1);
Serial.print(", ");
Serial.print(var2);
Serial.println();

Si bien no es una herramienta muy avanzada y tiene bastantes limitaciones sirve para visualizar datos visuales de una forma sencilla. Solo puede representar un tipo de gráfica, el tiempo en el eje horizontal y el valor de la variable en el eje vertical. Con este tipo de gráficas tienes el problema de que algunos datos son muy rápidos y serial plotter no permite almacenar la gráfica para revisarla.

Veamos un ejemplo donde se muestran varias gráficas simultáneamente:

float t = 0;
float oldDataT = 0;
float f = 1; //frecuencia
unsigned long oldMillis = 0;
unsigned long nowMillis = 0;

void setup() {
  Serial.begin(9600); //iniciamos el Serial para mostrar la gráfica
  while (!Serial) {}
}

void loop() {
  nowMillis = millis();
  double dt = double(nowMillis - oldMillis)/1000;
  
  t += dt;
  double tf = t*f;
  double sawtoothW = tf;
  double sinW = sin(tf*2*PI);
  double posSinW = sin(tf*PI);
  double squareW = (sin(tf*2*PI) > 0) ? 1: 0;
  double triangleW = (sin(tf*2*PI) > 0) ? 2*tf: 2-(2*tf);

  if(t - oldDataT > 0.05){ //si no lo datos van demasiado rápidos 
    oldDataT = t;
    Serial.print(sawtoothW);
    Serial.print(", ");
    Serial.print(sinW);
    Serial.print(", ");
    Serial.print(posSinW);
    Serial.print(", ");
    Serial.print(squareW);
    Serial.print(", ");
    Serial.print(triangleW);
    Serial.println();
  }

  oldMillis = nowMillis;    

  if(t >= 1/f){ //cuando alcanza un periodo se reinicia
    t = 0;
    oldDataT = 0;
  }
}

El resultado es el siguiente:

Gráficas en serial plotter

En la imagen se pueden ver gráficas de varias formas generadas desde Arduino.

Un ventaja que tiene este sistema es que los datos son compatibles con el formato CVS, habitual cuando se trabaja con datos.

Añadir guías a las gráficas

Hay algún truco que podemos usar para mejorar la visualización de las gráficas. Establecer guías, una guía no es nada más que una línea recta paralela al movimiento del gráfico y que tiene siempre un valor fijo. Se puede establecer una guía poniendo un valor fijo en los datos que se devuelven. Se usan como referencia visual. Si conoces los máximos y mínimos del valor puedes establecer otras dos guías una en cada valor y así evitar que la gráfica vaya dando «saltos» como a veces ocurre cuando varia mucho el valor.

Puedes ver el vídeo sobre este tema en mi canal de Youtube:

Haz click para ver el vídeo en Youtube

Filtro elimina banda o stop banda por software

Un filtro elimina banda es un filtro con dos frecuencias de corte, el filtro atenúa las frecuencias entre esas dos frecuencias de corte. Se podría ver cómo lo contrario a un filtro paso banda.

Una de las formas más simples de hacerlo es usar un filtro paso alto que deje solo las frecuencias por encima a la frecuencia de corte superior y uno paso bajo que deje solo las frecuencias inferiores a la de corte inferior. Para obtener el resultado sería necesario sumar de ambos filtros.

Veamos un poco de código:

//filtro paso bajo
double a_low = dt / (RC_low + dt)
output_low[t] = output_low[t-1] + a * (sensor[t] - output_low[t-1]); 

//filtro paso alto
double a_high = RC_high / (RC_high + dt);
output_high[t] = a_high * (output_high[t-1] + sensor[t] - sensor[t-1]);

//sumamos ambos
output = output_high[t] + output_low[t];

En nuestro simulador podemos usarlo para hacer una curiosa prueba, partiendo de una señal sinusoidal (verde), le añadimos ruido (azul) y usamos el filtro elimina banda para eliminar la señal y dejar solo el ruido (naranja):

Filtro elimina banda

Filtro paso alto por software

Un filtro paso alto es un filtro que fija una frecuencia de corte y deja pasar cualquier frecuencia superior a esa, mientras que atenúa frecuencias inferiores, Al menos esa es la teoría, en la realidad «el corte» no es tan limpio y se ven afectadas otras frecuencias. En realidad la frecuencia de corte e inferiores no sufren una caída completa a cero si no una gran atenuación.

En electrónica el modelo más sencillo de filtro paso alto es un filtro RC usando una resistencia y un condensador. En la imagen inferior se puede ver como montarla y como afecta a una señal ruidosa reduciendo el ruido de baja frecuencia, dejando solo la señal de alta frecuencia.

Filtro paso lato RC

Para realizar nuestro filtro paso bajo por software vamos a modelar matemáticamente el filtro RC.

double a = RC / (RC + dt);
output[t] = a * (output[t-1] + sensor[t] - sensor[t-1]);

Donde:

  • dt – Es el tiempo, en sg, entre las muestras t-1 y t
  • RC – Resistencia * Capacidad del condensador
  • output[t] – Es la señal filtrada en el momento t.
  • sensor[t] – Es la entrada sin filtrar en el momento t.

RC en nuestro caso es un valor numérico que emula el comportamiento Para calcular RC partiremos de la frecuencia de corte «fc» que deseemos en Herzios:

RC = 1 / (2 * Pi * fc)

Tras esta visión general vamos a los detalles.

Cuando t = 0 se puede usar como output:

output[0] = sensor[0]

Si no necesitamos guardar los valores no hace falta que usemos un array, podemos usar una sola variable output que almacena el valor del resultado anterior:

output = a * (output + sensor[t] – sensor[t-1]);

Veamos un ejemplo de señal (verde) sinusoidal sometida a ruido de baja frecuencia (azul) que la hace oscilar y filtrada usando un filtro de paso bajo (naranja):

Ejemplo de filtrado con filtro de paso alto

Puedes probar aquí un simulador que cree para mostrar como funcionan los diferentes filtros.

Puedes ver esta misma explicación para filtros de paso bajo.