Sensor de temperatura y humedad DHT11

Este es un sensor de temperatura y humedad muy popular dentro de los sensores de bajo costo y de fácil utilización, lo van a encontrar en proyectos con Arduino en un montón de sitios y también con otros microcontroladores.
En este caso lo estaré realizando en base al PIC, ya saben que los vengo usando desde hace mucho aunque también utilizo otras marcas, pero aqui en Argentina los PIC se consiguen casi en cualquier lado y a precios accesibles.
El sensor DHT11 es un sensor que nos proporciona temperatura y humedad relativa, en este sensor vamos a encontrar un sensor de temperatura del tipo resistivo NTC y un sensor de humedad del tipo resistivo similar al EMD4000, es decir, tanto para el sensor de humedad como el de temperatura requieren de una electrónica analógica para acondicionar la señal y luego una electrónica microcontrolada para leer los valores e integrarlos a la trama de datos serial, pero esto ya esta resuelto dentro del DHT11 y solo tendremos que conectarlo a un pin del microcontrolador mediante un pull-up.




Parametros electricos del DHT11
Es un sensor que posee solo 1% de resolución para la humedad y la temperatura, es decir, tendremos solo valores enteros, pero el protocolo que nos provee el DHT11 es enviando el valor entero de las unidades y decenas (0 a 255) y el valor de los decimales (0 a 255), tendremos entonces 2 byte para dada valor (para el de humedad y para el de temperatura), quedando así 4 bytes de datos y un quinto byte mas que sera de Checksum (se un proceso dato que se envía para comprobar la existencia de errores en la comunicación (sin entrar en muchos detalles)), este checksum sera de 1 byte, conformando así una trama completa de 5 bytes de datos que detallaremos mas adelante.

Este sensor nos proporciona una humedad que va entre el 20 y el 90 % de humedad relativa con una presicion de +/-5%, luego el sensor de temperatura va entre 0 y 50 °C con una precision de +/-2%, esta bien, no son los mejores valores ni los mejores rangos, pero este sensor esta pensado para el uso domestico donde podemos utilizarlo sin problemas y a un bajo costo, para valores mas amplios o mayor exactitud debemos buscar otro tipo de sensores como los de Bosch.

El sensor posee 4 pines de conexión aunque solo utilizaremos 3 pines, dos de alimentación y uno de datos, es un sensor que utiliza un protocolo de datos de 1 hilo, o One-Wire.
La tensión de alimentación va 3 a 5.5V lo que nos ayuda a usarlo en casi cualquier microcontrolador ya sea de 3V3 (500uA) o de 5V (2.5mA).
Los tiempo de propagación del mismo son algo lentos, es decir actualiza los datos cada unos 10s lo cual es lento, pero recordemos que los sensores atmosféricos no requieren de una alta velocidad de muestreo ya que los parámetros atmosféricos no suelen variar con tanta velocidad.

El cableado del sensor debe ser de un máximo de 20m según datasheet, para que no se pierdan datos y cada envió de datos por el puerto se realizara cada 4ms.

Protocolo de datos
El protocolo como mencionamos antes es de un solo hilo, es un protocolo sencillo pero al tener el clock junto con el dato requiere de algún trato extra que podría complicar un poco el proceso aunque es sencillo.

Como mencionamos antes serán 5 bytes de datos o 40 bits, estos bytes se componen por 1 byte de unidad/decena de temperatura, 1 byte de decimales de temperatura, 1 byte de unidad/decena de humedad, 1 byte de decimales de humedad y por ultimo 1 byte de checksum.
En este caso se conforma de esta manera:
8bit integral RH data + 8bit decimal RH data + 8bit integral T data + 8bit decimal T data + 8bit checksum.
De esta manera es como nos enviara los datos el sensor DHT11.
El checksum como mencionamos antes es un byte (en este caso es un byte) que lo que hace es sumar los valores de los demás bytes enviados, es decir, suma los valores de "8bit integral RH data + 8bit decimal RH data + 8bit integral T data + 8bit decimal T data", pero si sumamos 4 números de 8bits estaremos en un numero de 255+255+255+255 que nos dará 1024 y nos pasamos de los 8bit del checksum, entonces lo que hace es tomar los últimos 8bit de la suma y eso es lo que controla.

Ejemplo de Checksum.
el sensor nos envía 30.90° y 70.90%, esto si lo pasamos a bytes sera:
30, 90, 70, 90, que en binario sera: 00011110, 01011010, 01000110, 01011010, esto corresponde a los 4 bytes que envía el sensor DHT11, ahora si sumamos estos valores sera, 30+90+70+90 = 280, en este caso 280 es mayor a 255 por lo que nos pasamos de los 8 bits por ello solo tomaremos los ultimos 8 bits del valor, en este caso 280 es 100011000 como podremos ver son 9 bits y el DHT11 lo que nos enviara son los últimos 8 bits que seran 00011000 correspondiente al valor 24, entonces en este caso el DHT11 nos enviara: 00011110, 01011010, 01000110, 01011010, 00011000. Luego en nuestro programa debemos realizar el mismo proceso, recibimos los 4 bytes, los sumamos entre si y ese valor si es mayor a 255 lo movemos 1 vez a la izquierda, si es mayor a 511 lo moveremos dos veces para que siempre nos muestre el valor y lo compararemos contra el ultimo byte enviado por DHT11 si estos son iguales (osea el valor sumado por nosotros es igual al checksum que ha enviado el sensor) entonces el dato es correcto y podremos mostrarlo.

Por otro lado la trama es de un solo hilo, entonces tanto el microcontrolador como el sensor deben escuchar y hablar, no al mismo tiempo, pero si por turnos, cuando el microcontrolador le pide datos al sensor luego tiene que escuchar al sensor y recoger los datos, para esto el protocolo tiene tiempos establecidos en los bits del protocolo serial. Esto esta en el datasheet del sensor.


Aquí podremos ver las señales del microcontrolador (en negro) y del sensor (en gris), compartiendo el mismo bus de datos pero en tiempos distintos.
En el primer tramo podemos ver el MCU a nivel bajo como start y luego se pone a nivel alto en espera del sensor, entonces en este momento el MCU se comporta primero como salida y luego como entrada (esto tenemos que tenerlo en cuenta al momento de configurar el pin que usamos en el puerto).
Luego de que el MCU se pone a nivel alto como lectura, el sensor se pone a nivel bajo y el MCU lo lee, luego se pone a nivel alto como señal de que se reconoció la trama y comienza a enviar los datos unos y ceros, como podremos ver el cero o el uno se diferencia por el tiempo en que esta en High o Low.




Como podremos ver en estos diagramas de tiempo tenemos que respetar un determinado delay de tiempo entre ceros y unos, al momento de escuchar los bits del DHT como también al enviar la señal de start desde el MCU. Se podar ver mejor en el programa.

  1. #include <16F883.h>
  2. #FUSES NOWDT
  3. #FUSES HS
  4. #FUSES MCLR
  5. #use delay(clock=4000000)
  6. #use rs232(baud=9600,parity=N,xmit=PIN_C6,rcv=PIN_C7,bits=8)
  7. #define DATA PIN_B0                    //Pin del bus de un hilo para el DHT11
  8. unsigned int trama[5];                 //Vector donde se alojan los datos
  9. unsigned int recibeByte()              //Funcion que recibe un Byte
  10. {
  11.    unsigned int8 valorLeido = 0;       //Valor de retorno de la funcion
  12.    int8 i=0;
  13.    for(i=0; i<8; i++)                  //Iteracion para recepcion de bits
  14.    {
  15.       valorLeido <<= 1;                //Registro de desplazamiento de bits
  16.       while(!input(DATA));             //Espera a DATA = 0
  17.       delay_us(30);                    //Demora de 30us (Del Datasheet)
  18.       if(input(DATA))                  //Pregunta si DATA = 1
  19.       {
  20.           valorLeido |= 1;             //Realiza toggle del valor leido
  21.       }
  22.       while(input(DATA));              //Espera a DATA = 1
  23.    }
  24.    return valorLeido;                  //Retorna el valor leido
  25. }
  26. unsigned int recibeDato()              //Funcion que recibe el Dato
  27. {
  28.    int validacion = 0;                 //Variable de Validacion
  29.    int checksum = 0;                   //Variable de deteccion de cambios de secuencia
  30.    int8 j=0;                           //Variable para el lazo for
  31.    output_high(DATA);                  //Set DATA = 1  
  32.    output_low(DATA);                   //Set DATA = 0
  33.    delay_ms(18);                       //Demora de 18ms (Del Datasheet)
  34.    output_high(DATA);                  //Set DATA = 1
  35.    delay_us(25);                       //Demora de 25ms (Del Datasheet)
  36.    validacion = input(DATA);           //Mueve valor de DATA a Validacion
  37.    if(validacion)                      //Si Validacion = 1, Sensor no encontrado
  38.    {
  39.       printf( "Sensor no encontrado. \r");                      
  40.    }
  41.    delay_us(80);                       //Espera 80us (Del Datasheet)
  42.    validacion = input(DATA);           //Mueve valor de DATA a Validacion
  43.    if(!validacion)                     //Si Validacion = 0, Error de secuencia
  44.    {
  45.       printf( "Error en secuencia (Checksum) \r");  
  46.    }
  47.    delay_us(80);                       //Espera 80us (Del Datasheet)
  48.    for(j=0; j<5; j++)                  //Lazo de carga de bytes de datos
  49.    {
  50.        trama[j] = recibeByte();        //Carga del vector de datos
  51.    }
  52.    output_high(DATA);                  //Set DATA = 1
  53.    for(j=0; j<4; j++)                  //Lazo de carga de bytes de verificacion
  54.    {
  55.        checksum += trama[j];           //Carga de bytes de verificacion
  56.    }
  57.    if(checksum == trama[4])            //Si la secuencia de verificacion es correcta
  58.    {
  59.       return 0;                        //Se retorna 0 y se realiza la lectura
  60.    }
  61. }
  62. void main()
  63. {
  64.    setup_timer_0(RTCC_INTERNAL|RTCC_DIV_1);
  65.    setup_timer_1(T1_DISABLED);
  66.    setup_timer_2(T2_DISABLED,0,1);
  67.    while(TRUE)
  68.    {
  69.       if(recibeDato()==0)              //Si el retorno es 0, se imprime en terminal
  70.          printf( "Temp: %2u - R.H: %2u \r", trama[2], trama[0]);
  71.       delay_ms(500);
  72.    }
  73. }









Potenciómetro Digital con MCP41010 por SPI

En muchas ocasiones necesitamos controlar algún dispositivo analógico mediante resistencias variables como los potenciómetros, un potenciómetro de carbón como los que habituamos consta de una traza calibrada de carbón donde tendremos los dos extremos y un cursor móvil que sera el que moveremos para variar la resistencia, en algunos casos se moverá de forma rotativa y en otros casos de forma lineal, pero el funcionamiento es siempre el mismo.
Los potenciómetros analógicos poseen algunos problemas como el desgaste por el uso, la suciedad que puede generar ruido en el potenciómetro, muchas veces escuchamos en los controles de volumen un poco de ruido cuando giramos el potenciómetro, en la mayor parte de los casos se debe a la suciedad del mismo.
En cuanto al audio es algo no tan critico, pero si estamos controlando algún dispositivo de instrumentación o de sintonizador de radio, tener ruido en el potenciómetro es algo que nos juega en contra.
En esta nota veremos como reemplazar un potenciómetro analógico por uno digital que tendrá que ser controlado desde un microcontrolador ya que el mismo requiere un bus SPI para su control.
Existen otros potenciómetro digitales que no necesitan de un MCU, sino que tienen pines dedicados a pulsadores para hacer Up y Down (por ejemplo DS1669).
Elegir un potenciómetro con SPI en lugar de uno como el DS1669, tiene dos aspectos, el primero debería ser el funcional ya que controlar el mismo desde un MCU nos proporciona mas opciones ya que podremos controlar mas factores aparte del potenciómetro, y también el costo, en este caso un DS1669 es mucho mas costoso que el MCP41010 + un MCU.

Existen otros métodos para hacer un potenciómetro digital en base a resistencias y multiplexores, eligiendo distintos valores de resistencia en función del canal seleccionado del multiplexor, pero es un método un poco ruidoso e inexacto.

El potenciómetro digital tiene algunas limitaciones respecto del convencional, por ejemplo la tensión que podremos hacer circular por el mismo.
Recordemos que la corriente tanto en el digital como en el analógico debe estar calculada y limitada ya que podríamos romper el potenciómetro ya sea este digital o uno analógico convencional.
Pero la tensión en uno analógico podría ser muy elevada sin problemas.
En el digital tendremos un arreglo de mosfet en su interior que serán los que hacen las veces de resistencia, esto quiere decir que tendremos limitaciones eléctricas para su uso. 
En el caso del MCP41010 tendremos una tensión máxima a aplicar en el potenciómetro que sera igual a la VDD de alimentación.
Claramente son solo 5V, pero en la mayoría de las aplicaciones donde pondremos un potenciómetro de este estilo utilizaremos menos de 5V.

Una aplicación muy común de este potenciómetro es la de controlar la ganancia de realimentacion de un amplificador operacional, es decir, RF (Resistencia de Feedback) podrá ser controlada por el MCP41010 y así podremos variar la ganancia, luego el amplificador esta donde ustedes requieran.
También podríamos utilizar este potenciómetro digital para variar la tensión de offset de un operacional y así modificara de forma digital o automática.
Podríamos realizar un AGC (control automático de ganancia) usando una entrada del ADC del MCU mas la salida de este potenciómetro podríamos controlar la ganancia de un sistema.
También podemos controlar la polarizacion de un DiodoVaricap que podría estar en configuración Tanque con un oscilador y asi sintonizar un transmisor o receptor de radio.
Claramente hay muchas aplicaciones que dependerán de lo que ustedes requieran y en estos ejemplos nunca llegamos a los 5V.

El MCP41010 es un potenciómetro simple digital de 10k aunque también los hay de 50k y de 100k, tendremos que elegir el que necesitemos para nuestro proyecto.
También existe la seria MCP42XXX que serán dos potenciómetros en el mismo encapsulado.



Este potenciómetro tendrá 256 niveles de resistencia, es decir, setearemos entre 0 y 255, en el caso del potenciómetro de 10k serán saltos de 39 Ohms, Pero hay que tener en cuenta que hay un margen de error para este, en mi caso mide de 100Ohms hasta unos 9k, por ende los saltos serán diferentes.




El integrado posee en su interior una memoria EEPROM que servirá para almacenar la ultima posición del potenciómetro, es decir si apagamos nuestro dispositivo y lo encendemos nuevamente, tendrá que mantener el ultimo nivel de resistencia seteado.

El mismo posee con tres pines de datos para el Bus SPI que serán CS, SCK y SI, luego los pines de alimentación VSS y VDD (entre 2.7V y 5.5V) y por ultimo los del potenciómetro que serán PA0, PW0 y PB0, siendo PA0 y PB0 los extremos del potenciómetro y PW0 el cursor o toma central del mismo.



Para manejar el Bus SPI tendremos dos opciones, utilizamos el SPI por hardware que trae el microcontrolador o realizamos BitBanging (el protocolo a mano por software).
En mi caso he optado por la segunda opción ya que hace el código mas portable para otras plataformas.

Circuitos de aplicación de la hoja de datos:







Ahora tendremos que analizar la trama SPI que necesitamos para controlar este dispositivo.
A diferencia del I2C que este tiene solo dos hilos, donde uno sera el Clock SCL y el otro el Dato SDA, pero tanto el maestro como el esclavo deben comunicarse, esperar los ACK de comunicaciones, poner pines como entrada/salida según demanda, etc...
Aquí tenemos un hilo mas que nos facilita los datos, ya que tenemos uno de entrada uno de salida y uno de clock, MISO, MOSI y SCLK. (También tenemos un pin SS de selección pero que podremos obviarlo en algunas aplicaciones).

La trama es básicamente habilitar el bus, para eso pondremos a nivel bajo CS del MCP41010, luego enviaremos los bits de forma serial (en este caso serán 2 byte, uno para la dirección y otro para el valor) cada envió serie sera acompañado de un clock (sube y baja) y una vez terminado pondremos a nivel alto la habilitación del bus CS del MCP41010 para terminar la transacción.

Tendremos dos direcciones para el MCP41010, la 0x11 que sera para poner el dato en el potenciometro y la 0x21 que sera la dirección de la EEPROM interna para guardar el dato. Si guardamos el dato lo podremos recuperar cada vez que apeguemos y prendamos el circuito. En mi ejemplo no lo he utilizado pero podrían implementarlo sencillamente.




El programa:


  1. #include <16F883.h>
  2. #FUSES HS
  3. #FUSES MCLR
  4. #use delay(clock=4000000)
  5. #use rs232(baud=9600, xmit=pin_c6, rcv=pin_c7, bits=8, parity=N)
  6. #define CS   PIN_B2
  7. #define SCLK PIN_B1
  8. #define SI   PIN_B0
  9. void setPote(int valor)
  10. {
  11.    int8 i;
  12.    int8 palabra[2];
  13.    palabra[0]=valor;
  14.    palabra[1]=0x11;        //Posicion 0x11 asigna el valor, 0x21 guarda el valor
  15.    output_low(SCLK);
  16.    output_low(CS);
  17.    for(i=1;i<=16;++i) {
  18.       output_bit(SI, shift_left(palabra,2,0));
  19.       output_high(SCLK);
  20.       output_low(SCLK);
  21.    }
  22.    output_high(CS);
  23. }
  24. int8 valPote=0;
  25. void main()
  26. {
  27.    printf("Listo!\r");
  28.    while(true){
  29.       if(input(PIN_C0)==1)
  30.       {
  31.          if(valPote<255)
  32.          {
  33.             valPote++;
  34.             setPote(valPote);
  35.             printf("valPote: %3u \r",valPote);
  36.          }
  37.       }
  38.       if(input(PIN_C1)==1)
  39.       {
  40.          if(valPote>0)
  41.          {  
  42.             valPote--;
  43.             setPote(valPote);
  44.             printf("valPote: %3u \r",valPote);
  45.          }
  46.       }
  47.    }
  48. }

En el programa he agregado la comunicación UART para poder ver el valor en el terminal a medida que lo vamos modificando.
Todo lo correspondiente al bus SPI lo hace la función setPote() que se ha definido en el principio del programa donde utilizamos un vector de dos posiciones que alojara la dirección del MCP41010 (en este caso 0x11) y el valor a enviar al potenciómetro de forma dinámica.
Luego ponemos a LOW el Clock y la Habilitación.
Realizamos la iteración de los 2 Byte a enviar (las 16 iteraciónes), por cada iteración se moverá el dato hacia la izquierda realizando un shitregister que saldrá bit a bit por la salida de Datos.
Por cada iteración también tendremos que realizar un ciclo de Clock por ello ponemos a HIGH y luego a LOW el Clock.
Una vez finalizada las 16 iteración ponemos a nivel HIGH la habilitación y retornamos de la función.
Luego el programa principal realiza polling de dos pulsadores para para hacer un Up y un Down del potenciómetro mediante valPote-- y valPote++.

El circuito de prueba: