Problema con sensor de humedad SHT21. No consigo hacerlo funcionar.

Buenas tardes a todos. Soy Pablo, tecnico en mantenimiento y me gusta la programacion en C como hobby.
Estoy tratando de realizar la comunicacion I2C con varios sensores, ya lo consegui con el TC74A5 y ahora estaba tratando de comunicar con un SHT21, y por mucho que lo intente....mis conocimientos parece ser escasos cuando la cosa se complica un poco.
La idea de controlar estos sensores es para el control de riego de un huerto, ya que en los riegos inteligentes comerciales hay cosas que no me gustan y no puedo tocar...me gustaria hacer algo que pueda cambiar cuando requiera alguna modificacion o mejora.
El problema que tengo es que la comunicacion con el sensor aparentemente la hace pero de SSPBUF no entiendo por que siempre me coge 0. Además, estoy teniendo bastantes problemas a la hora de entender la resolucion de 12 bits para controlarlo con un PIC de 8 bits, lo estoy intentando hacer con el desplazamiento de bits,,,,pero seguro que algo estoy haciendo mal.
Y por ultimo, creo que la conversion de los 2 char del resultado de la lectura a la variable float me temo que no es asi.... Se puede pasar de un Int a un Float sin problema? o habria que tener cosas en cuenta.
Le agradeceria a quien me pudiera echar una mano....porque llevo dando golpes de ciego ya muchos dias, y no me hago con ello....
Adjunto los ficheros por si alguien más sabio que yo (que sera casi cualquiera) me podria decir donde falla mi codigo respecto a ese sensor.
El circuito es simplemente una bienvenida y da a elegir 2 modos (Temperatura y humedad), un pulsador de modo , y un pulsador de Entrar.
Y otra cosa, si con el registro de usuario cambias la resolucion a 8 bits igualmente pasaria la informacion en 2 Bytes. de los cuales los 2 menos significativos para el estado, los 8 siguientes el dato y los mas significativos restantes serian 0? Y la formula de obtencion del resultado de la humedad se seguiria usando en el denominador el 2^16?
Muchisimas gracias a todos.
Perdonad, me olvidaba. Estoy usando el MPLAB X IDE v3.65 con el XC8 v1.45.
 

Adjuntos

  • librerias.rar
    367.5 KB · Visitas: 7
Voy dejar de lado la comunicación I2C y suponer que todo está bien como decís (incluso, el no chequeo del checksum), del código que vi, en la función "sensor_read_sht21" encontré este error:

C:
...
dato_humedad = dato_sh[1] << 8;
dato_humedad = dato_sh[0];  //Acá está el error
...

El problema está en que pisas el dato a la hora de convertir los bytes individuales a un entero de 16 bits, el proceso debría ser así:

C:
dato_humedad = dato_sh[1] << 8; // dato_sh[1]= xxxx xxxx => dato_humedad = xxxx xxxx 0000 0000 (Tal como hiciste)
dato_humedad |= dato_sh[0]; //Que es lo mismo que hacer esto => dato_humedad = dato_humedad | dato_sh[0]; => Esa operación implica hacer una "OR" entre las variables => dato_sh[0]= yyyy yyyyy => dato_humedad = xxxx xxxx yyyy yyyyy
dato_humedad = dato_humedad >>2; //dato_humedad = 00xx xxxx xxyy yyyy

Finalmente para obtener el valor de la humedad, la hoja de datos te dice que hagas esta operación:

C:
dato_humedad = dato_humedad << 2; //dato_humedad = xxxx xxxx yyyy yy00 => Se limpian los 2 LSB por ser los bit de status, esta operación funciona en conjunto con la anterior >>2.
variable_flotante = ((125*dato_humedad)/65536) - 6; //Valor de la humedad relativa (RH)

Cabe destacar que ese calculo se podría mejorar bastante, ya que dividir por 65536 es lo mismo que desplazar a la derecha 16 veces, pero como no quiero complicar las cosas, te lo traté de dejar lo más entendible posible, tal como está en la hoja de datos.
 
Buenas tardes Cosmefulanito04, y muchas gracias por tu respuesta.
(Y perdona la tardanza en responder..., pero tengo al niño malo y con el trabajo... no he tenido tiempo de ponerme con ello)

C:
dato_humedad = dato_sh[1] << 8; //Esto lo entiendo. Primero cargo la parte alta.
dato_humedad |= dato_sh[0]; //Esto también lo entiendo, claro, si no hago una OR al estaria cargando '0' a los 8 bits mas significativos. Gran consejo¡
dato_humedad = dato_humedad >>2; /*Esto no lo entiendo. Creia que esto era para justificar a la derecha
el dato Int resultante  eliminando los 2 bits de estado. Pero  veo que luego corres 2 bits a la izquierda
para que queden en 0 los bits de estado. ¿Esto primero se debe a la resolucion de 12 bits no?
porque me estoy dando cuenta ahora que 12 bits de dato y 2 estado 14, nos sobrarian esos 2 bits que corres no?

Otra duda, ¿se puede concatenar sin problemas de un Int a un Float? No hace falta hacer nada especial?


Y respecto al codigo de I2C ya dudo que lo este haciendo bien. Me pensaba yo que lo estaba haciendo bien porque al poder controlar el TC74 sin problema, y al ser el mismo protocolo creia que no tendria que tener problemas, pero ya dudo de todo. ¿Me podrias decir si lo hago correcto? Porque la unica diferencia que veo respecto al TC74 es que este recibe 2 datos. El checksum de momento prefiero no usarlo, primero quiero conseguir recibir los 2 datos y luego cuando lo tenga me pongo con ello, por eso le pongo un Nack en el bit 45.
He pensado que quizas deberia liarme a colocar delays por toda la funcion, no vaya a ser que el proteus me este funcionando mal por eso.

C:
int sensor_read_sht21 (char slave_address, char ADD_control_byte)
{
    char dato_sh[2];
    int Temp_data;
    
    SSPCON2bits.SEN = 1; //Condicion de Start.
    while (SEN);   //Espera que termine la condicion de Start.
    
    PIR1bits.SSPIF = 0; // Limpio el flag de la interrupcion.
    SSPBUF = slave_address; //Cargo la direccion del esclavo en SSPBUF
    while (!SSPIF); //Espero al ACK, SSPIF salta cada 9no ciclo de reloj Mientras no salte la interrupcion no habrá terminado de mandar los datos
    PIR1bits.SSPIF = 0; // Limpio el flag.
    if (SSPCON2bits.ACKSTAT) //Si no ha recibido ACK, es decir que ACKSTAT =1
    {
        SSPCON2bits.PEN = 1; //Inicio el protocolo STOP
        while (PEN); // Mientras se termine de mandar el STOP
        return (0xFF); //Exit read No ACK
    }
    
    SSPBUF = ADD_control_byte; //Mando la direccion del registro del sensor donde está el dato de la memoria para la humedad
    while (!SSPIF);  //Espero al ACK
    PIR1bits.SSPIF = 0; //Limpio el flag.
    if (SSPCON2bits.ACKSTAT) //Si no ha recibido ACK, es decir que ACKSTAT =1
    {
        SSPCON2bits.PEN = 1; //Inicio el protocolo STOP
        while (PEN); // Mientras se termine de mandar el STOP
        return (0xFF); //Exit read No ACK
    }
    
    SSPCON2bits.RSEN = 1; //Restart, mando nuevamente la condicion de Start
    while (RSEN);
    PIR1bits.SSPIF = 0;
    SSPBUF = (slave_address + 1); //Mando la direccion del dispositivo +1 para decir que ahora va con el bit de escritura
    while (!SSPIF); //Espero al ACK
    PIR1bits.SSPIF = 0; //Limpio el flag
    if (SSPCON2bits.ACKSTAT) //Si no ha recibido ACK, es decir que ACKSTAT =1
    {
        SSPCON2bits.PEN = 1; //Inicio el protocolo STOP
        while (PEN); // Mientras se termine de mandar el STOP
        return (0XFF); //Exit read No ACK
    }
    
    SSPCON2bits.RCEN = 1; //Manda recepcion del Byte de temperatura del Slave
    while (!SSPSTATbits.BF); //Espera a recibir completamente el dato a SSPBUF
    dato_sh[1] = SSPBUF ; //cargo el Byte Mas Significativo en la variable global
                          
    SSPCON2bits.ACKEN = 1; // Mando 1 ACK al sensor desde el PIC
    while (ACKEN);
    
    SSPCON2bits.RCEN = 1; //Manda recepcion del Byte de temperatura del Slave
    while (!SSPSTATbits.BF); //Espera a recibir completamente el dato a SSPBUF
    dato_sh[0] = SSPBUF; //cargo el Byte Menos Significativo en la variable global
    SSPCON2bits.ACKDT = 1; //Coloco ACKDT en 1 para que me mande un NACK en vez de un ACK
    SSPCON2bits.ACKEN = 1; //Habilito para mandar un NACK.
    while (ACKEN); //Espero a que se complete el Nack
    
    SSPCON2bits.PEN =1 ; //Inicio la condicion de Stop
    while (PEN); //Espero a que se termine de mandar el Stop
    
    Temp_data = dato_sh[1] << 8;
    Temp_data |= dato_sh[0];
    Temp_data = Temp_data >>2;
    Temp_data = Temp_data << 2;
    return Temp_data;
}

Este codigo lo he modificado respecto a los datos que me diste el otro dia, mi pregunta es, al tener que recibir 2 datos tengo que activar nuevamente el Bit SSPCON2bits.RCEN ?y esperar con un while (!SSPSTATbits.BF) no?

Perdona, tengo demasiadas preguntas... Estoy fallando en algo y no logro encontrarlo. Gracias por tu tiempo .

Un saludo. Pablo.
 
Sobre el desplazamiento, la idea es borrar los dos bits menos significativos, ejemplo:

C:
#include <stdio.h>
#include <stdint.h>

int main()
{
    uint8_t my_byte;
    
    //Borrar bits inferiores usando desplazamientos
    my_byte = 0xff;    //Variable de 8 bits, que inicializo con todos los bits en 1 => my_byte = 0b 1111 1111
    my_byte = (my_byte >> 2); //Desplazo 2 lugares a la derecha
    printf("my_byte = 0x%02x\n", my_byte); //Imprimirá: "my_byte = 0x3f" => my_byte = 0b 0011 1111

    my_byte = (my_byte << 2); //Desplazo 2 lugares a la izquierda
    printf("my_byte = 0x%02x\n", my_byte); //Imprimirá: "my_byte = 0xfc" => my_byte = 0b 1111 1100
        
    //Borrar bits inferiores usando una máscara y aplicarle una operación AND
    my_byte = 0xff;    //Variable de 8 bits, que inicializo con todos los bits en 1 => my_byte = 0b 1111 1111
    my_byte = my_byte & 0xfC; //Realiza => 0b 1111 1111 AND 0b 1111 1100 = 0b 1111 1100
    printf("Resultado AND= 0x%02x\n", my_byte); //Imprimirá: "Resultado AND= 0xfc" => my_byte = 0b 1111 1100
    
    return 0;
}

Normalmente para este tipo de situaciones, se usa la opción 2, es decir una AND en conjunto a una máscara.

Te recomiendo que para este tipo de dudas (operaciones, flujos de programa, etc), usar un compilador en PC para probar (por ej. GCC + codeblocks) o utilizar un compilador online como este.

Sobre la conversión a flotante, es correcto, algo deberías hacer, eso se llama "casteo" y sería así:

C:
int dato_entero;
float variable_flotante;
...//Obtuve el valor entero
    
variable_flotante = ((125*(float)(dato_entero))/65536.0f) - 6;
/*
 (float)(...): acá indico que la variable "dato_entero" la interprete como un flotante.
 65536.0f: acá indico que la constante 65536 la interprete como flotante. Este último paso ya no sería necesario, porque el numerador ya fue interpretado como flotante.
*/

Con respecto a la comunicación, yo nunca usé PICs, así que no tengo experiencia en esa familia, pero pasame el modelo del microcontrolador y le doy una hojeada. De todas formas, algún compañero del foro que la tenga más clara, nos ayudará.
 
Menuda explicacion de corrimiento de bit buena que me has regalado ¡¡
Muchisimas gracias, me lo has dejado super facil para poder enterderlo.

Lo de los compiladores del PC lo veo interesante para hacer pruebas. En su dia use uno de Linux pero la verdad es que no lo he vuelto a tocar....Y para ver los resultado de estas operaciones es bastante rapido de comprobar. ¿Cual me recomentarias? Alguno es mas simple de usar?

Estoy usando un Pic16F887.

Muchas gracias por los consejos, enseñanza y su tiempo.
Un saludo. Pablo
Perdona, el compilador de PC Geany me valdria? Es el que use en su dia en Linux, para simular codigo en C. ¿O es peor que los otros?

Un saludo.
 
Geany es un IDE (entorno de desarrollo), como Codeblocks o Eclipse. El compilador de acceso gratuito que podés usar es el GCC que viene de Linux, pero lo podés instalar en windows usando algo llamado MinGW. O sea, deberías instalar Geany + MinGW (Gcc).

Volviendo al tema de la comunicación I2C, por lo que entiendo de la hoja de datos del Pic16F887, en la página 202 tenés un diagrama de señales cuando el I2C recibe como Master, ahí se ve que una vez que establecés la dirección del esclavo, tenés que setear RCEN en 1 y al finalizar la recepción del primer bytes se borra; por lo tanto, si, deberías volver a setear en 1 a RCEN c/vez que recibís un byte hasta terminar con la recepción total.

Edito:

Yo creo que hacés todo bien y supuestamente el CRC lo podés cancelar enviando el NACK en el bit 45 seguido del stop, como mencionastes arriba.
 
Última edición:
Gracias por tu respuesta.
Lo he probado seteando otra vez RCEN y nada de nada....
Simulando paso a paso en Proteus parece que comunica con el sensor , porque si que manda y recibe ACK pero a la hora de cargar el dato me manda siempre 0 en los 2 bytes de recepción .

Voy a probar a cablear el circuito, no vaya a ser que el Proteus me esté dando algún problema por tema de alguna temporización, como me ha pasado alguna vez más....

Te cuento cuando lo pruebe .

Muchas gracias por la ayuda !
Un saludo. Pablo.
 
Si, probalo con el sensor a ver que pasa.

Después que más o menos tengas todo funcionando, te recomendaría implementar time-outs a la rutina de comunicación del I2C, porque en varias ocasiones te quedás esperando en un while infinito la respuesta del sensor y podría pasar que si el sensor no está conectado o algo falla, el micro se queda clavado ahí. Lo mejor sería poner un time-out y en caso de pasar cierto tiempo sin respuesta, salir de la rutina e informar del error.
 
Te refieres a colocar temporizaciones de por ejemplo 2 segundos que si no sale del mismo while retornar un FF como hago con la respuesta del ACK?
No seria mala idea....
 
Na,,,, que pena....me sigue haciendo lo mismo en el circuito cableado....
Jo que rabia, lo peor es que ya no se ni una idea donde poder tirar, en que algo pueda estar mal... Porque parece que funciona la funcion y entra y direcciona el sensor....pero a la hora de coger los datos en SSPBUFF me carga 0 en los 2 datos,,, Y es lo que no entiendo... Voy a buscar por San Google a ver si alguien ha usado este sensor en XC8, porque casi todo el mundo usa CCS que ya te vienen la mayoria de las funciones hechas.... Muchas gracias por la ayuda Cosme¡
placa.jpg
 
Probá leer el registro de usuario:

C:
char Test_sensor_read_sht21(char slave_address)
{
    char registro_usuario;
   
    SSPCON2bits.SEN = 1; //Condicion de Start.
    while (SEN);   //Espera que termine la condicion de Start.
   
    PIR1bits.SSPIF = 0; // Limpio el flag de la interrupcion.
    SSPBUF = slave_address; //Cargo la direccion del esclavo en SSPBUF
    while (!SSPIF); //Espero al ACK, SSPIF salta cada 9no ciclo de reloj Mientras no salte la interrupcion no habrá terminado de mandar los datos
    PIR1bits.SSPIF = 0; // Limpio el flag.
    if (SSPCON2bits.ACKSTAT) //Si no ha recibido ACK, es decir que ACKSTAT =1
    {
        SSPCON2bits.PEN = 1; //Inicio el protocolo STOP
        while (PEN); // Mientras se termine de mandar el STOP
        return (0xFF); //Exit read No ACK
    }
   
    SSPBUF = 0xE7; //Lectura de registro de usuario
    while (!SSPIF);  //Espero al ACK
    PIR1bits.SSPIF = 0; //Limpio el flag.
    if (SSPCON2bits.ACKSTAT) //Si no ha recibido ACK, es decir que ACKSTAT =1
    {
        SSPCON2bits.PEN = 1; //Inicio el protocolo STOP
        while (PEN); // Mientras se termine de mandar el STOP
        return (0xFF); //Exit read No ACK
    }
   
    SSPCON2bits.RSEN = 1; //Restart, mando nuevamente la condicion de Start
    while (RSEN);
    PIR1bits.SSPIF = 0;
    SSPBUF = (slave_address + 1); //Mando la direccion del dispositivo +1 para decir que ahora va con el bit de escritura
    while (!SSPIF); //Espero al ACK
    PIR1bits.SSPIF = 0; //Limpio el flag
    if (SSPCON2bits.ACKSTAT) //Si no ha recibido ACK, es decir que ACKSTAT =1
    {
        SSPCON2bits.PEN = 1; //Inicio el protocolo STOP
        while (PEN); // Mientras se termine de mandar el STOP
        return (0XFF); //Exit read No ACK
    }
   
    SSPCON2bits.RCEN = 1; //Manda recepcion del Byte de temperatura del Slave
    while (!SSPSTATbits.BF); //Espera a recibir completamente el dato a SSPBUF
    registro_usuario = SSPBUF ; //Cargo el registro de usuario
                         
    SSPCON2bits.ACKDT = 1; //Coloco ACKDT en 1 para que me mande un NACK en vez de un ACK
    SSPCON2bits.ACKEN = 1; //Habilito para mandar un NACK.
    while (ACKEN); //Espero a que se complete el Nack
   
    SSPCON2bits.PEN =1 ; //Inicio la condicion de Stop
    while (PEN); //Espero a que se termine de mandar el Stop
   
    return registro_usuario;
}

A diferencia de la lectura que hacés, en esta rutina el sensor solo te devuelve un byte que por defecto debería valer 0x02.
 
Última edición:
Muchas gracias por responder Cosme! Por lo que veo esa función sería para ver si la config de usuario está en Default no? .

Voy a activar antes la función de soft reset para dejar por Default el registro de usuario para ver qué hace . Aunque en principio por lo que leo en el manual por defecto va a resolución de 12 bits de humedad.

Además voy a comprobar el valor de registro de usuario para ver que este como dices.


En cuando pruebe esto te comento....a ver si mañana por la tarde me da tiempo .

Un saludo . Pablo
 
Bien, eso significa que el sensor responde al pedido que hacés por I2C y devuelve algo esperado.

Ahora está en ver si el problema está en la comunicación o en la conversión de datos, de la rutina que hiciste, yo dudaría del uso de variables int, de hecho para microcontroladores, lo ideal sería usar un tipo de variable que sea más específica que son estas:

C:
#include <stdint.h> //Librería necesaria para el tipo de variables que menciono abajo

// Variables con signos
int8_t mi_variables; //Entero de 8 bits con signos => 1 byte => equivalente a un char
int16_t mi_variables; //Entero de 16 bits con signos => 2 bytes
int32_t mi_variables; //Entero de 32 bits con signos => 4 bytes

// Variables sin signos
uint8_t mi_variables; //Entero de 8 bits sin signos => 1 byte => equivalente a un unsigned char
uint16_t mi_variables; //Entero de 16 bits sin signos => 2 bytes
uint32_t mi_variables; //Entero de 32 bits sin signos => 4 bytes

De tu rutina, yo haría estas modificaciones:

C:
int sensor_read_sht21 (char slave_address, char ADD_control_byte)
{
    uint8_t dato_sh[2];
    uint16_t Temp_data;
    ...
}

Además, si tenés acceso a un puerto serie o incluso con el mismo LCD, podrías imprimir los dos bytes que te devuelve el I2C previo a la conversión.

Si el problema continúa, es tema en la comunicación I2C cuando tenés que recibir más de un byte, algo ahí se nos estaría escapando.
 
Ya me funciona¡¡

Antes de realizar la transmision con el sensor lo que he hecho por probar es realizar un Soft Reset que se supone que deja la configuracion del sensor por default.

Creo que esta prueba ya la habia hecho antes, pero como tenia mal el paso de los datos char al Int como me comentaste del |= para cargar bien los datos me dio algun dato absurdo y obvie que el softreset no habia valido para nada ejecutarlo.

Por cierto, me gusta la forma de declarar las variables que tienes.
uint8_t dato_sh[2];
uint16_t Temp_data;

De esta manera puedes decir que un int tenga 8 bits? yo creia que un Int siempre tenia 2 Bytes.
Se puede hacer lo mismo con float?
Mi forma de declarar variables siempre ha sido muy basica...no sabia que se podian hacer esas variantes. Vienen incluidas en el compilador XC8? o esta en stdio o alguna de esas?
En el ejemplo que me has puesto u (de unsigned) int 16 (16 bits de largo) _t ( quiere decir algo?

Muchas gracias Cosme¡¡ No lo habria logrado sin tu ayuda. Muchas gracias de veras¡¡ Cuando tenga el proyecto funcionando lo subo completo por si a alguien novato como yo le puede servir para apoyarse en el.
Un saludo. Pablo
 
Buenísimo.

Con respecto a ese tipo de variables, viene del estándar c99 (link), es decir cada cierto tiempo se le da retoques al lenguaje C y se agregan cosas nuevas, este tipo de definiciones se agregaron en el 99 y seguramente cuando aprendiste el lenguaje (como me pasó a mi), no estaban usando ese estándar (o mejor dicho, no te lo explicaron). Yo las descubrí hace no "mucho", unos 10 años (son del 99).

La cuestión es, que la variable int o unsigned int, depende de la arquitectura en la que trabajás, a veces puede ser de 16 bits y otras por ej de 32 (pc), lo mismo pasa con los short. Entonces, para evitar ese problema, te conviene usar este tipo de variables que dejan bien en claro de cuantos bits será la variable.

Además tiene otro beneficio que es una mejor lectura del código, por ej. usar una variable char/unsigned char para un valor numérico de 8 bits, es un despropósito, pierde legibilidad, es mucho más claro usar int8_t, a pesar de que en el fondo es un char, pero en tu código, queda claro que su uso es para un valor numérico y no un caracter.

Por lo que vi, este tipo de variables, las tenés en el compilador de PIC, acordate que es necesario agregar la librería.

Con respecto a los flotantes, se mueven bajo una mecánica completamente diferente, si ves los bits crudos, siguen un estandar llamado IEE 754. En resumen, hasta donde se, con la simple definición "float", le estás diciendo que la variable es de 32 bits (sin importar la arquitectura) y que tiene que ser interpretada según el estándar que te puse arriba. O sea, no hay confusión por el tamaño de la variable y el nombre es lo suficientemente indicativo para ser legible en el código.
 
Última edición:
Muchisimas gracias por la gran explicacion (otra vez)...entiendo que utilizando la libreria que mencionas no tendria problema en usar esa deficinicion, que es verdad, que asignando 8bits a 1 int siendo puramente un entero el codigo queda más claro, y dejando los char para caracteres.

Ahora que me mencionas lo del standar C99... tuve muchos problemas en su dia con el. Actualice el MPLAB a la version 5.30 y el Xc8 a la version 2.30 creo recordar....en las opciones de esa version del compilador me daba la opcion de elegir C90 o C99. Y la verdad es que programas que me funcionaban un dia al dia siguiente no me funcionaban...vamos....era como si no encontrara las librerias ....me canse de eso porque me desanime mucho.
Y por ello, me descargue la version mas antigua que use en su dia de MPLAB 3.65 y XC8 1.45 y no he tenido ningun problema...( de momento)
El C99 tiene muchas variaciones de texto respecto al C90? es decir, se programa de escribe manera diferente?, como si fueran en cierta manera compiladores parecidos pero diferentes? Y los registros reservados del Pic cambian?? o solo son mejoras que se le van añadiendo como las definiciones de las variables que comentas?

Un saludo.
 
Yo creo que son cosas que se agregan, es decir de c90 a c99 no creo que haya tantos problemas, a lo sumo nuevos warnings, el problema podrá estar en ir para atrás. Probaría a ver que pasa.
 
Atrás
Arriba