esp8266

Hace algunas semanas compré por Ebay 3 módulos ESP8266 con la esperanza de poder utilizarlos en aplicaciones de IoT. Oí hablar de este módulo en Hackaday y solo leer de lo que era capaz no me lo pensé dos veces:

  • Procesador con puertos GPIO (General Purpose Input Output)
  • Wifi integrado
  • ¡Solo vale 4€! (Actualización: actualmente es posible encontrarlo por ~1.5€)

Por tanto, este chip es capaz de conectarse con la red Wifi de tu casa, guardar o escribir información en una base de datos, hacer las veces de servidor, leer información de sensores, activar otros actuadores (como relés)… ¡Las posibilidades son enormes! Es un pequeñísimo Arduino con Wifi integrado.

Los primeros meses desde la salida del ESP8266 fueron bastante oscuros, ya que no se tenía apenas información, no había un SDK oficial y lo que había estaba en chino.

Pero recientemente la comunidad ESP8266 ha empezado a florecer ofreciendo diferentes vertientes por las que poder configurarlo. Yo me voy a centrar únicamente en la que utiliza el firmware NodeMCU.

Este firmware hace correr un compilador de LUA en el procesador, de manera que podremos escribir los programas que queramos correr en este mismo lenguaje.

Para poder instalar el firmware solo hay que conectar un FTDI (USB-UART converter) a nuestro ESP8266 de la siguiente manera:

esp826

Advertir que es obligatorio alimentar el ESP8266 a 3.3V, ya que por lo contrario quemaréis el chip.

Para entrar en modo de escritura de firmware hay que conectar el pin GPIO0 a masa. Yo he utilizado un jumper con que puedo conectar y desconectar fácilmente el pin.

Cuando tengamos este montaje hecho, necesitaremos descargar el grabador del firmware, que lo podemos encontrar también en el GitHub de NodeMCU.

Una vez descargado, abrimos el flasher:

ESP8266-flasher-folder

Con lo que nos aperecerá la siguiente aplicación:

ESP8266-flasher

Automáticamente cuando conectemos nuestro FTDI cambiará el puerto COM. Si no te sucede asegúrate de tener los drivers para tu FTDI instalados.

Flashear el ESP8266 con NodeMCU es tan sencillo como darle al botón Flash(F), y se pondrá a escribir el firmware en la memoria del procesador. Como se puede ver en la pestaña Config hace cuatro escrituras distintas, siendo la tercera la que más tiempo lleva. Cuando termine el FTDI se desconectará del equipo y aparecerá un símbolo verde abajo en la esquina izquierda. En mi caso dicho símbolo no ha aparecido pero el flasheo ha sido correcto igualmente.

Ahora que ya tenemos el firmware subido, tenemos que cargar nuestro programa. Para ello utilizaremos el programa LUAUploader de Hari Wiguna. Con esta aplicación especialmente desarrollada para el ESP8266 podremos subir nuestros programas de una manera muy sencilla.

Cabe destacar que la corriente que entrega un puerto USB del ordenador no es suficiente para iniciar el módulo de Wi-Fi, así que a partir de este momento es necesario alimentar el ESP8266 con una fuente externa. En mi caso he utilizado dos pilas LR03 (las pequeñas de los mandos) para obtener 3V. Recuerda que no sirven los 5V o el chip se quemará. Cuando conectes la fuente externa desconecta el pin de 3.3V del FTDI para evitar problemas, pero deja el pin de GND para unificar las masas del FTDI y de la fuente externa.

Una vez lo tengamos todo listo podemos pasar a lo divertido. Abrimos el LUAUploader y nos aparecerá la siguiente pantalla:

LuaUploaderPara comprobar que el firmware ha sido grabado correctamente iremos a la pestaña “Immediate” y escribiremos un pequeño chunk de prueba (un chunk es cada trozo de código que Lua puede ejecutar)

dummy = 5
print(dummy)

Si el compilador nos devuelve 5, quiere decir que el firmware ha sido escrito correctamente:

LUAtest

 

Para poder conectarnos a nuestra red Wi-Fi es necesario introducir el siguiente chunk:

ip = wifi.sta.getip()
print(ip)
--nos devolverá nil
wifi.setmode(wifi.STATION)
wifi.sta.config("SSID","password")
ip = wifi.sta.getip()
print(ip)
--nos devolverá la IP asignada

Hay veces que después de intentar conectarse a la red Wi-Fi continúa devolviendo nil. Probad a imprimir de nuevo la dirección IP con:

ip = wifi.sta.getip()
print(ip)

Ahora, para hacer una pequeña prueba, podemos montar un servidor en el propio ESP8266 con el chunk:

srv=net.createServer(net.TCP) 
srv:listen(80,function(conn) 
   conn:on("receive",function(conn,payload) 
      print(payload) 
      conn:send("<h1>Hola Ruben!</h1>")
   end) 
   conn:on("sent",function(conn) conn:close() end)
end)

Una vez subido, si accedemos a la dirección del ESP8266 nos devolverá la página con un heading y el texto “Hola Ruben!” (para poder escribir acentos hay que utilizar los códigos ASCII en HTML y utilizar la columna HTML Número: é = &#233;)

web

Podéis encontrar muchos más ejemplos en el GitHub de NodeMCU.

Para la creación de vídeo, cada píxel se guarda con 8 bits (valores de 0-255). Si se guarda de manera diferencial, los valores pueden ir de -255 a 255. Por tanto, necesitamos 9 bits para codificar cada píxel. La codificación diferencial se utiliza en vídeo porque en muchos casos, las diferencias entre fotograma y fotograma son pequeñas. De esta manera, si se codifica de manera diferencial, es posible ahorra bits. Sin embargo, si las diferencias suelen ser grandes, como ocurre en un cambio de escena, este tipo de codificación no es rentable.

Como en vídeo sí se utiliza codificación diferencial, es necesario intercalar fotogramas codificados de manera absoluta (llamados keyframes) para evitar que un error en un fotograma se propague hasta el final del vídeo. En vídeos que no se supone que se tienen que editar se coloca un keyframe cada 100~200 fotogramas, en TDT se coloca un keyframe cada 0.5 segundos y en vídeos para edición un keyframe cada 2~3 fotogramas.

Además, la información no es diferencial bit a bit, sino que utiliza el movimiento de una zona. Para detectar si el contenido de un fotograma está en el siguiente, se divide la imagen en bloques y se busca el grupo de píxels entero. Si el tamaño de estos bloques fuese muy pequeño, habría que informar del movimiento de muchos bloques, por lo que consumiría muchos bits. Es por ello que los bloques tienen un tamaño de 16×16 y se les llama macrobloques (MB). Sin embargo, para buscar el movimiento los saltos del MB no son de 16 en 16 sino que son más pequeños.

En MPEG-2 se definen 3 tipos de imagen:

  • Intra (I): tiene su información guardada en el propio frame por lo que no se codifica diferencialmente. Un error en I se propaga hasta la siguiente I ya que todo el resto de tipos, depende de su información.
  • Predicción (P): tiene una codificación diferencial, y su información solo puede estar referida a imágenes anteriores intra o otras también predictivas.
  • Bidireccional (B): utiliza información de referencia frames futuros y pasados de imágenes I, P  o una imagen intermedia, pero nunca de otras B. Por tanto, un error en una imagen B no se propaga. La imagen intermedia es el calculo de la imagen media a partir de la referencia futura y la referencia pasada. Se guarda el menor error entre la media, la pasada y la futura. Para guardar la media, el codificador debe guardar los dos vectores de movimiento.

Por tanto, en la codificación y decodificación se necesita más memoria para almacenar las imágenes hasta que se satifagan las dependencias.

Demostrar que la información mutua es positiva \(I\left( X, Y \right) \geq 0\) y simétrica \(I \left( X, Y \right) = I \left( Y, X \right)\)

Para demostrar que la información mutua es positiva, tenemos que definir los valores de la información mutua entre dos símbolos. Para ello debemos tener en cuenta los valores extremos de \(P\left( x_i | y_j \right)\).

\(P\left( x_i | y_j \right) = 1\). Esta situación se da cuando tenemos un canal ideal sin ruido en el que si hemos recibido \(y_j\) es porque hemos enviado \(x_i\). De esta manera:
\[I \left( x_i, y_j \right) = \log{\frac{P \left( x_i | y_j \right)}{P\left( x_i \right)}} = \log{\frac{1}{P\left( x_i \right)}} = I \left( x_i \right)\]
En el otro extremo, tenemos cuando en el canal, la salida Y es independiente de X, por lo que es el peor canal que podemos tener. De esta manera, \(P\left( x_i | y_j \right) = P\left( x_i \right)\) por ser independientes.
\[I \left( x_i, y_j \right) = \log{\frac{P \left( x_i | y_j \right)}{P\left( x_i \right)}} = \log{\frac{P \left( x_i \right)}{P\left( x_i \right)}} = \log{1} = 0\]
 

Por tanto, queda demostrado que
\[0 \leq I \left( x_i, y_j \right) \leq I \left( x_i \right)\]
 

Por otra parte, queda demostrar que la información mutua es simétrica. Aplicando Bayes en la definición:
\[I \left( x_i, y_j \right) = \log{\frac{P \left( x_i | y_j \right)}{P\left( x_i \right)}} = \log{\frac{P \left( x_i , y_j \right)}{P\left( x_i \right) P\left( y_i \right)}} = \\ = \log{\frac{P \left( y_j | x_i \right) P\left( x_i \right)}{P\left( x_i \right) P\left( y_i \right)}} = \log{\frac{P \left( y_j | x_i \right) }{ P\left( y_i \right)}} = I \left( y_j, x_i \right)\]

Como hemos visto en la entrada anterior Concepto de la información, el término información nos da información sobre los posibles mensajes que una fuente puede producir. Sin embargo este término no es útil para describir a la fuente. Debido a que una fuente no está diseñada en torno a un solo mensaje, sino al conjunto de todos los mensajes que puede transmitir, podemos describir una fuente en términos de la información media producida. Esta información media transmitida se conoce como entropía de una fuente.

Si tenemos un alfabeto de longitud m \(X = \{ x_1, x_2, …, x_m \}\), la información del símbolo j es \(I_j = -\log{\frac{1}{P_j}}\). Si el número de símbolos enviados en el mensaje es \(N\), donde \(N >> 1\), el símbolo j se envía \(N \cdot P_j\) veces. Por tanto, la cantidad de información que se envía es:

\[ N P_1 I_1 + N P_2 I_2 + … + N P_m I_m = \sum_{i = 1}^{m}N P_i I_i~~~bits \]

La entropía de una fuente es el valor promedio de la información de cada uno de los símbolos de la fuente. Por tanto, si dividimos la expresion (\(\ref{eq:informacion_mensaje}\)) por el número de símbolos enviados \(N\), tenemos el valor medio en bits por símbolo:

\[ H\left(X \right) = E \{ X \} = \sum\limits_{i = 1}^{n} I_i \cdot P\left(x_i \right) = – \sum\limits_{i = 1}^{n} P\left(x_i \right) \cdot \log[P\left(x_i \right)] \]

con unas unidades de bits/símbolo.

Pero, ¿cuál es el significado de la entropía tal y como aparece en la ecuación de la entropía. Aunque no sepamos cuál es el siguiente símbolo que una fuente va a transmitir, en media podemos esperar conseguir \(H\) bits de información por símbolo o \(N\cdot H\) bits en un mensaje de N símbolos, si N es grande (mucho mayor que 1).

La entropía mide la cantidad de información que una fuente puede dar en un tiempo determinado

Rango de valores de la entropía de una fuente

De acuerdo, ya tenemos un parámetro para caracterizar una fuente. Veamos qué valores puede tomar este parámetro.

El mínimo de entropía se da cuando una fuente no da información. La fuente no tiene libertad de elección y siempre envía el mismo mensaje. Por tanto, la entropía es 0.

Por otra parte, la máxima entropía de una fuente se da cuando la fuente tiene máxima incerteza o lo que es lo mismo, máxima libertad de elección. Cada símbolo es igual de probable que el resto, por lo que \(P_j = \frac{1}{m}\) siendo \( m \) el número de símbolos. Así, \(\sum_{i=1}^{m}P_i I_i\) \(= \frac{1}{m} \log{\frac{1}{\frac{1}{m}}} \cdot m\) \(= \log{\frac{1}{\frac{1}{m}}} = \log{m}\).

De esta manera:

\[ 0 \leq H \leq \log{m} \]

Tasa de entropía

Ahora imaginemos que introducimos la variable de tiempo en la situación. Supongamos que tenemos dos fuentes con la misma entropía pero una es más rápida que la otra (i.e. enviando más símbolos por unidad de tiempo). De esta forma, la entropía no nos da ninguna información acerca de la velocidad con la que transmite la fuente. Es por ello que necesitamos de otro parámetro llamado tasa de entropía en la que informa de los bits enviados por segundo.

\[ R = \frac{H}{\bar{\tau}} \]

donde \( \bar{ \tau }  \) es la duración media por símbolo que se define como

\[ \bar{\tau} = \sum_{j=1}^{m} P_i \tau_i \]

y que \( 1/ \bar{\tau}\) equivale al número medio de símbolos por unidad de tiempo.

Tasa de transmisión: 14 Mb/s
Ancho de banda: 5 MHz
\(\left.P_{Tx}\right|_{EB} = 43~dBm\)
Factor de ruido del receptor: \(\left.F\right|_{Rx} = 4~dB\)
Frecuencia de la portadora \(f_c = 900~MHz\)
Ganancia del transmisor: \(G_{Tx} = 15~dB\)
Ganancia del terminal de usuario: \(G_{UE} = 0~dB\)

Modelo a utilizar:
– \(f_c = 900~MHz\)
– Altura de la antena transmisora: \(h_{EB} = 15~m\) sobre edificios

\[B = 5 MHz\]

 

Ancho de banda nominal (MHz) 1.4 3 5 10 15 20
Ancho de banda ocupado (MHz) 1.08 2.7 4.5 9 13.5 18
Número de RB (UL o DL) 6 15 25 50 75 100
Número de subportadoras 72 180 300 600 900 1200

\[B = 5 MHz \rightarrow Tabla \rightarrow N_{RB} = 25\]
\[L_{prop} = 120.9 + 37.6 \log{R}~[km]\]

De las pérdidas de propagación también podemos extraer el parámetro \(\gamma\) ya que \(10\gamma\) es el número que multiplica el término \(\log{R}\).
\(\gamma = 3.76\)

\[\eta = throughput = \frac{14~Mb/s}{5~MHz} = 2.8~b/s/Hz\]

 

shannon_bound_lte
De la gráfica podemos ver que la SINR(dB) equivalente es aproximadamente 11 dB. También podemos sacarlo de la fórmula del límite de Shannon corregida \(\eta = \frac{C}{B} = 0.75 \log_2{(1+SNR)}\)
\[SINR(dB) = 11dB\]
\[SINR(dB) < CINR(dB)\]

\[\left.CIR(dB)\right|_{sectorización} = 10\log{ \left[3 \cdot \frac{1}{6} \left( \frac{D}{R} \right)^\gamma\right]} = 10\log{ \left(\frac{1}{2} q^\gamma\right)}\]

¿Qué k es válido?
\[k = 1 \rightarrow CIR=5.96~dB\]

\[k = 3 \rightarrow CIR=14.96~dB\]

\[k = 4 \rightarrow CIR=17.27~dB\]

Por tanto \(k = 3\), ya que el mínimo k optimiza el despliegue (es más barato).

\[L_{total} (dB) = 121.5 + \left.10\log{\left( 1 – \frac{10^{\frac{11~dB}{10}}}{10^\frac{14.92~dB}{10}} \right)}\right|_{k=3} + 43~dBm – 4~dBm – 10 \log{25} – 11~dB = 133.26~dB\]

\[L_{prop} (dB) = L_{total} (dB) + G_{Tx}(dB) + G_{UE}(dB) = 133.26 + 15~dB + 0~dB = 148.26~dB\]

\[L_{prop} (dB) = 120.9 + 37.6 \log{R} = 148.26~dB\]

\[\log{R} = \frac{148.26-120.9}{37.6} \Rightarrow R = 10^{\frac{148.26-120.9}{37.6}} \approx 5.34~km\]

Uno de los casos más complicados y que engloban todas las posibles dificultades en el cálculo de la polarización es en el caso de tener una onda plana que no se propaga según los ejes.

Primero vamos a comenzar por recordar cómo se pueden clasificar las diferentes polarizaciones de onda electromagnética.

  1. Polarización lineal: la onda electromagnética se propaga variando su amplitud en una línea.
  2. Polarización circular: la onda electromagnética dibuja una circunferencia.
  3. Polarización elíptica: la onda electromagnética se propaga dibujando una elipse.

Para averiguar con qué polarización se propaga una onda electromagnética se tienen que satisfacer las siguientes condiciones:

  1. Polarización lineal: se da cuando una de las dos componentes perpendiculares es nula o cuando el desfase entre ambas es múltiplo de \(\pi\) (\(0\), \(\pi\), \(2\pi\), …).
  2. Polarización circular: la onda electromagnética tiene dos componentes de la misma amplitud y se encuentran desfasadas un multiplo entero de \(\frac{\pi}{2}\), por lo que la onda electromagnética. Si \(\phi_2 – \phi_1 > 0\) se trata de polarización circular a izquierdas (o levogira) mientras que si \(\phi_2 – \phi_1 < 0\) se trata de polarización circular a derechas (o dextrogira).
  3. Polarización elíptica: no se satisfacen ninguno de los requisitos anteriores.

Algoritmo general de análisis de polarización

  1. Definir vector unitario del sentido de propagación \(\hat{\mathbf{k}}\): es el vector que acompaña la exponencial compleja cambiado de signo.
  2. Escoger una base unitaria en la que descomponer la amplitud \(\mathbf{e_1}\). Para calcular la polarización, hay que escribir la amplitud de la onda electromagnética en dos vectores perpendiculares al vector de propagación. Por tanto, podemos escoger cualquier vector que sepamos que sea perpendicular al vector \(\hat{\mathbf{k}}\).
  3. Calcular la base perpendicular a \(\hat{\mathbf{k}}\) y \(\mathbf{e_1}\). Para ello hay que realizar el producto vectorial \(\hat{\mathbf{k}} \times \mathbf{e_1}\).
  4. Descomponer la amplitud del campo en las bases \(\mathbf{e_1}\) y \(\mathbf{e_2}\) en forma compleja polar. Es tan sencillo como hacer el producto escalar de la amplitud por el vector unitario de ambas bases.
  5. Dividir el módulo de \(E_{01}\) entre \(E_{02}\) y restar \(\phi_2 – \phi_1 \) y verificar a qué caso de polarización pertenece.

Ejemplo

\[\mathbf{E} = \left[ 4\hat{\mathbf{x}} + \left(1+\sqrt{3}j \right)\hat{\mathbf{y}} – \left(5+\sqrt{3}j \right)\hat{\mathbf{z}} \right]e^{-j \pi \left(x + y +z \right)}\]

    1. Vector unitario del sentido de propagación: \(\hat{\mathbf{k}} = \frac{\mathbf{x} + \mathbf{y} +\mathbf{z} }{\sqrt{3}}\)
    2. Escoger base unitaria perpendicular a \(\hat{\mathbf{k}}\): \(\mathbf{e_1} = \frac{\mathbf{x}-\mathbf{y}}{\sqrt{2}}\)
    3. Calcular base unitaria perpendicular a \(\hat{\mathbf{k}}\) y \(\mathbf{e_1}\): \[\mathbf{e_2} = \hat{\mathbf{k}} \times \mathbf{e_1} = \frac{1}{\sqrt{3}\sqrt{2}} \begin{vmatrix}\mathbf{x} & \mathbf{y} & \mathbf{z} \\ 1 & 1 & 1 \\ 1 & -1 & 0 \end{vmatrix} = \frac{\hat{\mathbf{x}} + \hat{\mathbf{y}} -2\hat{\mathbf{z}}}{\sqrt{6}} \]
    4.  Reescribimos la onda original con las nuevas bases \(\mathbf{e_1}\) y \(\mathbf{e_2}\).\(E_{01}\) \(= \mathbf{E} \cdot \mathbf{e_1} \) \(= \left( 4\hat{\mathbf{x}} + \left(1+\sqrt{3}j \right)\hat{\mathbf{y}} – \left(5+\sqrt{3}j \right)\hat{\mathbf{z}} \right)\cdot \left( \frac{\hat{\mathbf{x}}-\hat{\mathbf{y}}}{\sqrt{2}} \right)\) \(= \sqrt{6} e^{-j \frac{\pi}{6}}\)\(E_{02}\) \(= \mathbf{E} \cdot \mathbf{e_2} \) \(= \left( 4\hat{\mathbf{x}} + \left(1+\sqrt{3}j \right)\hat{\mathbf{y}} – \left(5+\sqrt{3}j \right)\hat{\mathbf{z}} \right)\cdot \left(\frac{\hat{\mathbf{x}} + \hat{\mathbf{y}} -2\hat{\mathbf{z}}}{\sqrt{6}} \right)\) \(= \sqrt{42} e^{j 0.33}\)\(\mathbf{E}\) \(= \left[ \sqrt{6} e^{-j \frac{\pi}{6}} \frac{\hat{\mathbf{x}}-\hat{\mathbf{y}}}{\sqrt{2}} + \sqrt{42} e^{j 0.33} \frac{\hat{\mathbf{x}} + \hat{\mathbf{y}} -2\hat{\mathbf{z}}}{\sqrt{6}} \right]e^{-j \pi \left(x + y +z \right)}\)
    5. Comprobar las condiciones: \(\frac{E_{02}}{E_{01}} = \frac{\sqrt{42}}{\sqrt{6}} \approx 2.64\), \(\phi_2 – \phi_1 = 0.33 + \frac{\pi}{6} \approx 0.8565\) Como no se satisface ninguna de las condiciones para polarización lineal ni circular, se trata de una polarización elíptica a izquierdas (diferencia de fases positiva).

Por fin podemos hablar de el primer proyecto más o menos serio que he hecho con ESP8266 y esta vez se trata de un medidor de nivel agua con los datos guardados en la nube, de manera que sean accesibles desde cualquier sitio.

Principios de funcionamiento

Para empezar, imaginemos que tenemos una cisterna donde tenemos agua. Primero debemos asumir que las medidas de la cisterna se mantienen constantes en todo momento, esto es, ancho, largo y alto. Para conocer el volumen de agua que reside en el interior tan solo necesitaremos saber cuál es la altura del agua, ya que es la única variable del sistema.

Con esto claro, necesitamos un mecanismo que sea capaz de medir a qué altura de encuentra el agua. Mi idea ha sido utilizar un sensor ultrasónico, cuyo funcionamiento es el siguiente:

  1. El altavoz emite un sonido de frecuencia ultrasónica (imperceptible al oido humano)
  2. Se pone un cronómetro en marcha.
  3. El micrófono recibe la señal que había sido emitida por el altavoz.
  4. Se para el cronómetro que se había puesto en marcha al emitir el sonido
  5. Se calcula sabiendo la velocidad del sonido, la distancia a la que está el objeto sobre el cual ha rebotado la pulsación ultrasónica.

hc

Materiales

Para empezar los materiales que he utilizado son:

  • ESP-01 (ESP8266)
  • Fuente de alimentación de 3.3V
  • Fuente de alimentación de 5V
  • Programador FTDI
  • Sensor ultrasónico HC-SR04

Montaje hardware

El módulo ultrasónico HC-SR04 tiene 4 pines:

  1. Vdd: debemos conectar 5V. No podemos conectar directamente los 3.3V con los que alimentamos el ESP-01. Una lástima.
  2. Trig: es el encargado de dar la orden al altavoz de que emita el pulso.
  3. Echo: es el micrófono que espera el rebote del pulso emitido.
  4. GND: es importante unificar la masa del ESP-01 con la propia de la fuente de 5V para evitar problemas de grounding.

En mi caso he utilizado los 5V que tiene disponible el Arduino, aunque es algo bastante engorroso. Para la versión final, utilizaré un convertidor lineal (para conseguir los 3.3V) y una sola alimentación.

La conexión entre el ESP-01 y el HC-SR04 es extremadamente sencilla:

  1. GPIO0 – Echo
  2. GPIO2 – Trig

Software

Para hacer funcionar el HC-SR04 con NodeMCU, he utilizado el código que ha subido sza2 en GitHub modificándolo un poco.

Se trata de dos archivos: init.lua y hcsr04.lua. Cabe destacar que el ESP8266 ejecuta init.lua en cuanto se enchufa. La base de su funcionamiento reside en el uso de la función tmr.alarm(), que ejecuta una función cada intervalo de tiempo que nosotros definamos. En este caso, la función de medir distancia.

tmr.alarm(0, 3600000, 1, function() send(device.measure()) end)

El código de sza2 envía vía serie el resultado cada 0,5s, pero podemos configurarlo como nosotros queramos. Solo recordar que hay que introducir el tiempo en milisegundos.

En el código original se imprime por pantalla el resultado, pero como hemos dicho anteriormente nuestro objetivo es que la lectura sea subida a un servidor para poder ver el nivel de agua desde cualquier sitio.

Para ello he utilizado una plataforma llamada ThingSpeak, que sirve precisamente para esto. Su funcionamiento es muy sencillo. La información se guarda en los que ThingSpeak llama canales. En cada canal podemos añadir hasta 8 variables que queramos monitorizar, y la manera en que podemos guardar nuevos datos es haciendo una petición HTTP con el método GET. Las variables que tenemos que incluir en la petición HTTP es el código del canal (para saber dónde hay que guardar la información), el número del campo a guardar seguido del valor.

conn:send("GET /update?key=CodigoAPI&field1="..value1.." &amp;field2="..value2.."HTTP/1.1\r\n")

De esta manera quedará registrado automáticamente en ThingSpeak, como podemos ver en este applet:

También decir que el HC-SR04 devuelve valores contiguos que difieren unos de otros por motivos obvios, por lo que he modificado el código para que devuelva el valor medio de 500 medidas.

El código completo del archivo init.lua es:

dofile("hcsr04.lua")
wifi.setmode(wifi.STATION)
wifi.sta.config("SSID","pass")
print(wifi.sta.getip())
device = hcsr04.init()
print("Loading...")
 
function send(value)
  if value ~= -1 then
    print(value)
    conn=net.createConnection(net.TCP, 0)
    conn:on("receive", function(conn, payload) print(payload) end)
    conn:connect(80,'184.106.153.149')
    conn:send("GET /update?key=CodigoAPI&field1="..value.." HTTP/1.1\r\n")
    conn:send("Host: api.thingspeak.com\r\n")
    conn:send("Accept: */*\r\n")
    conn:send("User-Agent: Mozilla/4.0 (compatible;ESP8266; Windows NT 5.1)\r\n")
    conn:send("\r\n")
  end
end
 
tmr.alarm(0, 60000, 1, function() send(device.measure()) end)
hcsr04 = {};
 
function hcsr04.init(pin_trig, pin_echo)
	local self = {}
	self.time_start = 0
	self.time_end = 0
	self.trig = pin_trig or 4
	self.echo = pin_echo or 3
	gpio.mode(self.trig, gpio.OUTPUT)
	gpio.mode(self.echo, gpio.INT)
 
	function self.echo_cb(level)
		if level == 1 then
			self.time_start = tmr.now()
			gpio.trig(self.echo, "down")
		else
			self.time_end = tmr.now()
		end
	end
 
	function self.measure()
          medidas = 500
          mean = 0
		for i=1,medidas,1 do
			gpio.trig(self.echo, "up", self.echo_cb)
			gpio.write(self.trig, gpio.HIGH)
			tmr.delay(100)
			gpio.write(self.trig, gpio.LOW)
			tmr.delay(100000)
			if (self.time_end - self.time_start) < 0 then
				return -1
			end
			mean = mean + (self.time_end - self.time_start) / 58
		end
		print(mean/medidas)
		return mean/medidas
	end
	return self
end

Una manera de representar una señal periódica, es mediante su serie de Fourier. La serie de Fourier de una función tiene la ventaja de expresar la misma función como una suma de senos y cosenos. Por ejemplo, una señal como un tren de pulsos, que tiene una expresión analítica poco rigurosa matematicamente hablando, se puede descomponer como suma senos y cosenos o lo que es lo mismo, sumas de exponenciales complejas.

Si tenemos una señal v(t) con un periodo \(T_0 = 1/f_0\) , su desarrollo en serie de Fourier exponencial es: \[v(t) = \sum_{n = -\infty}^{+\infty} c_n e^{j2\pi n f_0 t} ~~~~~ n = 0, 1, 2, …~~~(1)\]

Los coeficientes de la serie se pueden calcular como \(c_n = \int_{T_o} v(t) e^{-j2 \pi n f_0 t}dt\)

Una manera intuitiva de interpretar esta expresión, es pensar en el término \(c_n\) como el peso que tiene la frecuencia \(n\cdot f\) dentro de la señal v(t). De esta manera se promedia el producto \(v(t) e^{-j2 \pi n f_0 t}\) para todas las frecuencias.

Cabe destacar que \(c_n\) puede ser un (y normalmente son) números complejos, que se pueden expresar en forma polar como \(c_n = \left| c_n \right| e^{j~arg~c_n}\)

Otras consideraciones importantes son:

  • Todas las frecuencias son multiplos enteros o armónicos de la frecuencia fundamental \(f_0 = 1/T_0\), por lo que la distancia entre líneas espectrales es uniforme.
  • La componente continua es el valor promedio de la función.
  • Si una función es real en tiempo, el espectro frecuencial tendrá simetría par para la amplitud e impar para la fase.

Para poner un ejemplo práctico, vamos ha calcular la serie de Fourier para un tren de pulsos rectangulares con periodicidad \(T_0\) cuya expresión analítica es:
\[ v(t)= \left\{\begin{matrix} A & \left|t \right| < \tau/2\\ 0 & \left|t \right| > \tau/2 \end{matrix}\right. \]

pulse

Aplicando la definición:
\[c_n = \frac{1}{T_0} \int_{-T_0/2}^{T_0/2} v(t) e^{-j2 \pi n f_0 t}dt = \frac{1}{T_0} \int_{-\tau/2}^{\tau/2} A e^{-j2 \pi n f_0 t}dt =\]\[= \frac{A}{-j2\pi n f_0 T_0} \left( e^{-j \pi n f_0 \tau} – e^{+j \pi n f_0 \tau}\right) = \frac{A}{T_0} \frac{\sin{\left( \pi n f_0 \tau \right)}}{n f_0 \tau} =\]\[= \frac{A}{T_0} sinc \left(n f_0 \tau\right)\]

Como hemos visto, si manipulamos la expresión de la serie de Fourier podemos llegar a una expresión compacta en la que aparece la función sinc.

Por tanto, si ahora fueramos capaces de generar los infinitos coeficientes \(c_n\), sustituyendolos en (1) seriamos capaces de reconstruir el tren de pulsos cuadrados a la perfección. Si por el contrario, solo calculamos un número finito de ellos, la función resultante tendría un aspecto parecido al de la imagen, donde se aprecia el fenómeno de Gibbs:

fourier-serie
Serie de Fourier de un pulso cuadrado, con un número finito de coeficientes. Fuente: Wikipedia

La animación anterior está implementada con la librería JavaScript P5.js. El código fuente es el siguiente:


let time = 0;
let path = [];
let slider;

function setup(){
	createCanvas(600, 400);
	slider = createSlider(0, 25, 3, 1);
	slider.position(20, 380);
}

function draw(){
	background(124,124,124);
	textSize(20);
	fill(255);
	text(' n = ' + slider.value(), width/2, 30);
	translate(200, 200);

	stroke(255);

	let x = 0;
	let prevx = 0;
	let y = 0;
	let prevy = 0;
	let c = color(255);

	for(let i = 0; i < slider.value(); i++){
		fill(c);
		stroke(255);
		let n = 2*i + 1;
		let amp = 100 * 4/(n * TWO_PI);
		x += amp * cos(n * time);
		y += amp * sin(n * time);
		// Point
		ellipse(x, y, 5);
		line(prevx, prevy, x, y);
		noFill();
		// Circle
		ellipse(prevx, prevy, amp * 2);
		prevx = x;
		prevy = y;
	}

	path.unshift(y);

	translate(200, 0);

	beginShape();
	for(let i = 0; i < path.length; i++){
		vertex(i, path[i]);
	}
	endShape();

	line(x-200, y, 0, path[0]);

	if(path.length > 200){
		path.pop();
	}

	time += 0.05;
}

Fuente: elaboración propia

Anteriormente hemos hablado del concepto de información y de entropía en una fuente de comunicaciones, parámetros necesarios para poder modelar matemáticamente una fuente de información en comunicaciones. Toda fuente quiere enviar información a un destino, el usuario y de manera obligatoria debe utilizar un canal mediante el cual enviar esta información. En comunicaciones móviles sería el aire, en comunicaciones ópticas la fibra óptica o en comunicaciones guiadas podrían ser cables coaxiales, multifilares, etc.

Por tanto, es necesario disponer de herramientas para modelar el canal, ya que usualmente suele ser un entorno hostil en el que está presente ruido, interferencias y demás impedimentos que dificultan la comunicación. Normalmente, los terminales de comunicación se consideran perfectos (sin ruido, sin distorsión, etc.) y es en el canal en el que se tienen en cuenta todas estas no idealidades. Por tanto el canal se considera el vehículo de transmisión al que se le suman todos los fenómenos que tienden a limitar la comunicación. El hecho de que haya límites físicos para el envío de información nos lleva a la noción de capacidad del canal.

Sistema de comunicación
Sistema de comunicación tal y como aparece en el paper de Shannon en 1984

Del mismo modo que la entropía mide la cantidad de información que una fuente puede dar en un tiempo determinado, la capacidad del canal mide la cantidad de información que un canal puede transmitir por unidad de tiempo.

Teniendo en cuenta la capacidad del canal, el teorema fundamental de la teoría de la información se puede reescribir como:
\(C\) y una fuente con una tasa de entropía \(R\), si \(R \leq C\), existe una técnica de codificación tal que la salida de la fuente puede ser transmitida a lo largo del canal con una frecuencia de errores arbitrariamente pequeña a pesar de la presencia de ruido. Si \(R > C\), no es posible transmitir sin errores.

Caracterización de un canal

Un canal se caracteriza estadísticamente. La caracterización de un canal supone conocer la probabilidad de tener a su salida todos los símbolos que la fuente puede transmitir.

Canal discreto sin memoria
Canal discreto sin memoria

En un canal discreto sin memoria (los símbolos son independientes), la función de verosimilitud es la que caracteriza el canal

\[ P\left(Y | X \right) = \{ P\left(y_j | x_i \right) \} \]

La ecuación nos dice cuál es la probabilidad de que a la salida del canal haya el símbolo Y habiendo transmitido X, pues el receptor debe adivinar cuál ha sido X (el valor realmente enviado por la fuente) habiendo recibido el símbolo Y.

Otras definiciones que también se emplean son:
\[ P\left(X = x_i\right) = P\left( x_i\right)\]

La probabilidad a priori es propia de la fuente y nos dice cómo de probable es que un símbolo (\(x_i\)) se transmita.
\[ P\left(X | Y \right) = \{ P\left(x_i | y_j \right) \} \]
\[ P\left(X = x_i, Y = y_j \right) = P \left( x_i | y_j \right) P \left(y_j \right) = P \left(y_j | x_i \right) P \left( x_i \right)\]

La ecuación quiere decir que la probabilidad de enviar \(x_i\) y recibir \(y_j\) es la probabilidad a posteriori (enviar \(x_i\) habiendo recibido \(y_j\)) por la probabilidad de recibir \(y_j\) o del mismo modo, es la función de verosimilitud (probabilidad de recibir \(y_j\) habiendo enviado \(x_i\)) multiplicado por la probabilidad de enviar \(x_i\).

Dicho de otro modo más, ¿cuál es la probabilidad de tener \(x_i\) a la entrada e \(y_j\) a la salida? Si \(x_i\) fuese independiente de \(y_j\), la probabilidad debería ser \(P\left(x_i\right) \cdot P\left(y_j \right)\). Sin embargo, si tuviésemos un canal en el que su salida fuese independiente de su entrada sería una auténtica basura. Por eso es necesario tomar las probabilidades condicionadas. Probabilidad de tener \(y_j\) habiendo enviado \(x_i\) por la probabilidad de enviar \(x_i\).

Resumen

\(P\left( x_i\right)\) es la probabilidad de que la fuente seleccione el símbolo \(x_i\) para transmitir. (Probabilidad a priori)
\(P\left( y_j \right)\) es la probabilidad de que el símbolo \(y_j\) haya sido recibido en el destino (al otro lado del canal).
\(P\left( x_i, y_j \right)\) es la probabilidad conjunta de que \(x_i\) sea transmitido y \(y_j\) sea recibido.
\(P\left( x_i | y_j \right)\) es la probabilidad condicional de que \(x_i\) sea transmitido dado que \(y_j\) haya sido recibido. (Probabilidad a posteriori)
\(P\left( y_j | x_i \right)\) es la probabilidad condicional de que \(y_j\) sea recibido dado que \(x_i\) haya sido transmitido. Esta probabilidad incluye las probabilidades de error de símbolo. (Función de verosimilitud)

Ejemplo de canal discreto sín memoria

Canal binario borrador \(BEC(\epsilon)\)

\(P\left(Y = 0 | X = 1\right) = 0\)
\(P\left(Y = 1 | X= 1 \right) = 1 – \epsilon\)
\(P\left(Y= ? | X = 1 \right) = \epsilon\)
\(P\left(Y = 0 | X = 0 \right) = 1 – \epsilon\)
\(P\left(Y = 1 | X = 0 \right) = 0\)
\(P\left(Y = ? | X = 0 \right) = \epsilon\)

Información mutua \(I\left(X, Y \right)\)

Canal probabilidad

En el ejemplo de arriba, tenemos una fuente con dos símbolos y un destino con tres símbolos. Si el sistema está diseñado para entregar \(y_j = y_1\) cuando \(x_i = x_1\) y \(y_j = y_2\) cuando \(x_i = x_2\), entonces la probabilidad de error de símbolo viene dada por \(P\left( y_j | x_i \right)\) para \(j \neq i\).

Una descripción cuantitativa de la información transferida en el canal es mediante la información mutua.
\[I\left(x_i, y_j \right) = \log_2{\frac{P\left( x_i | y_j \right)}{P\left( x_i \right)}}~~~bits\]
La información mutua mide la cantidad de información transferida cuando \(x_i\) es transmitido y \(y_j\) es recibido.

Rango de la información mutua

El máximo se consigue en el caso de tener un canal ideal sin ruido. Es decir, cuando se recibe \(y_j\) es porque se ha enviado \(x_i\). Por tanto \(P \left( x_i | y_j \right) = 1\) y \(I\left(x_i, y_j \right) = \log_2{\frac{P\left( x_i | y_j \right)}{P\left( x_i \right)}} = \log_2{\frac{1}{P\left(x_i\right)}} = I\left(x_i \right)\), en el que la información transmitida es la de \(x_i\).

El mínimo se da cuando el ruido del canal es tan grande que la salida del canal es independiente de la entrada, por lo que \(P \left( x_i | y_j \right) = P\left( x_i \right)\). En este caso\(I\left(x_i, y_j \right) = \log_2{\frac{P\left( x_i | y_j \right)}{P\left( x_i \right)}} = \log_2{\frac{P\left(x_i\right)}{P\left(x_i\right)}} = 0\)
\[0 \leq I\left(x_i, y_j\right) \leq I\left( x_i \right)\]
En la mayoría de los casos, los canales estan entre estos dos extremos. Para analizar el caso general, se define la información mutua media:
\[I\left(X, Y \right) = \sum_{i=1}^{N}\sum_{j=1}^{M} P\left(x_i, y_j\right) I\left( x_i, y_j\right)\]
\[ =\sum_{i=1}^{N}\sum_{j=1}^{M} P\left(x_i, y_j\right) \log_2{\frac{P\left( x_i | y_j \right)}{P\left( x_i \right)}}~~~bits/simbolo\]
\(I\left(X, Y \right)\) representa la cantidad información media ganada por símbolo recibido, que debe diferenciarse de la información media por símbolo de la fuente representado por \(H\left(X\right)\)

Equivocación o entropía condicional

Utilizando relaciones de probabilidad se puede llegar a expresiones equivalentes de la información mutua.
\[P\left(x_i, y_j \right) = P\left(x_i | y_j \right)P\left(y_j \right) = P\left( x_i | y_j \right)P\left( x_i \right)\]
\[P\left( x_i \right) = \sum_{j=1}^{N} P\left( x_i, y_j \right)~~~~P\left( y_j \right) = \sum_{i=1}^{N} P\left( x_i, y_j \right)\]
\[\log{\frac{a}{b}} = \log{\frac{1}{b}} – \log{\frac{1}{a}}\]
Si empleamos estas relaciones en , llegamos a:
\[I\left(X, Y\right) = \sum_{i=1}^{N}\sum_{j=1}^{M} P\left(x_i, y_j \right) \log_{2}{\frac{1}{P\left(x_i \right)}}\\-\sum_{i=1}^{N}\sum_{j=1}^{M}P\left(x_i, y_j \right)\log_{2}{\frac{1}{P\left(x_i | y_j\right)}}\]
De manera que el primer término se simplifica de la siguiente manera:
\[\sum_{i=1}^{N}\left[ \sum_{j=1}^{M} P\left( x_i, y_j \right)\right] \log_{2}\frac{1}{P\left(x_i \right)} = \sum_{i=1}^{N} P\left(x_i \right) \log_{2}\frac{1}{P\left(x_i \right)} = H\left(X \right)\]
Por lo tanto,
\[I\left(X, Y\right)= H\left(X\right)-H\left(X|Y\right)\]
En donde \(H\left(X|Y\right)\) es la equivocación o entropia condicionada, que representa la información perdida en el canal ruidoso.

La ecuación dice que la información media transferida por símbolo es la misma que la entropía de la fuente menos la equivocación.

Volviendo a la ecuación , debido a que \(P\left(x_i | y_j \right)P\left(y_j \right) = P\left( x_i | y_j \right)P\left( x_i \right)\), se demuestra que \(I\left(X, Y\right) = I \left(Y, X\right)\), y que por tanto,
\[I\left(X, Y\right)= H\left(Y\right)-H\left(Y|X\right)\]
En la ecuación se dice que la información transferida es igual a la entropia del destino \(H\left(Y\right)\) menos la entropía del ruido \(H\left(Y|X\right)\) añadido por el canal. La interpretación de \(H\left(Y|X\right)\) como entropía de ruido sigue la observación anterior de que \(P \left( y_j | x_i \right)\) incluye las probabilidades de error de símbolo.

Screen Shot 2014-11-04 at 12.17.23

Frecuencia de resonancia \(f_r=\frac{1}{2\pi\sqrt{LC}}\)

Para el calculo de la resistencia parásita serie:

\[R_s = \frac{L\omega}{Q}\]

donde \(Q = \frac{f}{BW}\)

Si \(Q \geq 5\):

\[R_p=\frac{L}{R_s\cdot C}\]

A la frecuencia \(f_r\) el circuito es puramente resistivo. El valor de su impedancia entonces es \(R_p\)