jueves, 10 de septiembre de 2020

ADC Lectura de Celda con HX711

Medición de Peso con PIC16F


Este sitio fue creado para compartir el mutuo interés en la electrónica y en especial la programación de microcontroladores PIC.

Ahora veremos como realizar una medición de peso utilizando un modulo muy comercial que utiliza el chip HX711. Nuestro objetivo es poder medir con el PIC16F887 la magnitud en gramos o kilogramos haciendo uso de una celda de carga y el modulo amplificador HX711. El valor final del peso, se enviara como mensaje mediante una comunicación serial con el ordenador PC.

La programación se realizara utilizando el compilador XC8 y el entorno de desarrollo integrado MPLABX disponibles en la pagina de microchip de forma gratuita, por lo que sera necesario contar con conocimientos previos de microcontrolador y lenguaje C.



1. Celda de carga

Una celda de carga eléctrica permite traducir la fuerza aplicada sobre una estructura, en una señal de voltaje lineal que un microcontrolador puede medir utilizando un amplificador y convertidor ADC.

Fig1. Tipos de celdas

La celda esta compuesta por un metal y al menos una galga extensométrica adherida en su interior de tal manera que cuando aplicamos una fuerza en ella, el metal se deforma cambiando la resistencia de la galga y como esta se conecta utilizando un arreglo conocido como puente wheatstone, su salida entrega una señal de voltaje proporcional a la fuerza aplicada. El metal de la celda se calcula para soportar un rango de fuerza ya sea de tensión, compresión o ambos limitando la deformación que sufre a una capacidad máxima de carga. Si se excede este limite el metal sufre una deformación permanente que imposibilita su regreso al punto inicial.

Fig2. Puente Wheatstone 1, 2 y 4 galgas

El voltaje de salida de una celda es lineal con amplitudes que se encuentran en el orden de los milivoltios, y el fabricante denota este valor proporcionando la variación mV por cada Voltio de alimentacion. Por ejemplo si la hoja de datos de una celda indica que la salida es 1mV/V, esto nos dice que si alimentamos la celda con 10V el valor final a su máxima capacidad de carga sera 10mV.

En caso de conectar mas de una celda en paralelo, la capacidad de carga total se suma y la señal de salida permanece igual siempre que utilicemos un circuito sumador conocido como combinador, caso contrario se divide por la cantidad de celdas conectadas.

Fig3. Conexión en paralelo

La conexión en paralelo a menudo requiere utilizar resistencias adicionales de bajo valor que se calculan para compensar la variación nominal y distribución de carga entre las celdas, los detalles respecto a este punto no se consideran en este articulo del blog, pero la siguiente imagen ilustra un circuito con todos lo elementos necesarios para este fin.

Fig4. Circuito de compensación

Uno de los factores externos que afecta a una celda eléctrica es la temperatura, y por esta razón es necesario registrar el voltaje de salida sin peso, para luego considerar la variación que tiene y compensar la diferencia utilizando un peso conocido. De esta manera sera posible calcular la magnitud en gramos o kg.

La celda de carga que utilizare es una MicroCell SC133 que presenta las siguientes características.
  • Capacidad de carga = 5000gr
  • Precisión = 0.05% 
  • Voltaje de excitación = 3 - 14V  
  • Relación de Salida =  1.0± 0.15mV/V
  • Efecto de la temperatura = 0.05 %F.S./10°C
Fig5. Celda de un solo punto
2. Modulo Amplificador HX711
Como la salida de una celda de carga es un voltaje analógico de muy baja amplitud, sera necesario amplificar la magnitud para su tratamiento. En un sistema digital esta señal deberá convertirse a un valor digital utilizando un convertidor analógico a digital ADC.

El circuito integrado HX711 disponible únicamente en formato SOP de 16 pines incorpora un amplificador de ganancia programable (PGA) con un convertidor Analógico-Digital (ADC) de 24-bit para dos canales. Ademas es posible conseguir este IC como un modulo compacto ya con los componentes minimos para su funcionamiento, tal como se observa en las figura 6 y 7, estos sin duda nos facilita la conexion de la celda de carga con nuestro microcontrolador.

Fig6. Esquema del modulo HX711

Fig7. Modulo Integrado HX711

Alguno detalles adicionales del Amplificador ADC HX711 son:
  • El voltaje de alimentación de 2.6 a 5.5V
  • Rango de temperatura de -40 a 85°C 
  • Ganancia canal A ajustable de 64 o 128
  • Ganancia canal B fija de 32
  • Rango entrada diferencial AVDD(E+)/Ganancia
  • Salida serial sincronizada de 24-bit
Un dato importante con respecto a la lectura de datos, es que el pin RATE permite ajustar el tiempo a 10Hz (RATE = 0) o 80Hz (RATE = 1), por lo tanto sera necesario verificar en el modulo a que nivel esta conectado este pin.
 
La comunicación serial utiliza un pin de entrada SCK para recibir pulsos de sincronización del microcontrolador y un pin de salida DT para enviar los datos digitales al microcontrolador. Así mismo la salida DT cambia a nivel bajo cuando hay datos listos para enviar, esperando entre 25 y 27 pulsos de sincronización para enviar cada uno de los bits.

Durante una lectura el microcontrolador debe enviar mínimo 25 pulsos de reloj, y opcionalmente los pulsos 26 y 27 para determinar con que canal y ganancia se trabajara.

Fig8. Tabla para selección de canal y ganancia

Análisis del Amplificador HX711
Si el rango de voltaje en la salida del amplificador a escala completa es AVDD/Ganancia, si alimentamos la celda con 5V entonces es posible utilizar las siguientes escalas para la entrada:
  • ±80mV si la ganancia es 32, solo en canal B
  • ±40mV si la ganancia es 64, canal A
  • ±20mV si la ganancia es 128, canal A
La salida digital de 24-bit de este Amplificador esta en formato complemento a dos dentro del rango que va desde un valor mínimo de 800000h (Vi- > Vi+) a máximo de 7FFFFFh (Vi+ > Vi-), una manera de obtener un valor absoluto de la salida digital es aplicar en la salida una suma exclusiva de 0x800000, con esto se lograra ajustar los valores a:
Valor Negativo (Vi- > Vi+) Rango absoluto de 000000h a 7FFFFFh 
Valor Positivo  (Vi+> Vi-)   Rango absoluto de 800000h a FFFFFFh

Analisis de la Celda SC133
Si la celda se alimenta con 5.0V con su relación de salida 1.0mV/V nos proporcionara un voltaje de 5mV (5.0 * 0.001) a su máxima capacidad de carga (5Kgr). Con este valor podemos determinar que la mejor escala de trabajo en la entrada de nuestro amplificador HX711 es el canal A con ganancia 128, que nos permite trabajar en ±20mV (configuracion por defecto), entonces considerando la escala asignada podemos determinar que la relación de voltaje por bit en la salida del Amplificador sera: RV = 0.040V/2^24
Ahora con estos valores conocidos podemos calcular la relación de bits por unidad de peso, por ejemplo si ejercemos un peso de 100 gramos sobre la celda de carga su voltaje de salida sera 5mV*100gr/5000gr=0.1mV, entonces la salida digital del amplificador HX711 para este voltaje sera 0.1mV/RV = 41943. A continuación se muestra una tabla con diferentes valores para el calculo:
    Peso (gr)    Voltaje (mV)    Salida (24-bit)
    5000              5mV        2097152  ->  200000h
    1000              1mV         419430  ->  066666h
     100            0.1mV          41943  ->  00A3D7h
      10           0.01mV           4194  ->  001062h
       1          0.001mV            419  ->  0001A3h
 Tabla 1. Relación de salida por unidades de peso en gramos.
Tomar en cuenta que el resultado de 24-bit recibido del amplificador HX711 esta en formato complemento a dos, y para obtener un valor absoluto se debe sumando de forma exclusiva el valor 800000h.
Otro punto a considerar es la precisión de la celda que de acuerdo a la especificación técnica indica un 0.05% a maxima carga, por lo tanto (5000 gr) * (0.05/100) = 2.5 gramos representaría nuestro margen de error en la salida con relación al peso exacto en la celda. Para convertir este error en un valor digital podemos multiplicarlo por la relación de la salida para cada gramo que se muestra en la tabla 1, es decir  2.5gr * 419 = 1047.5, esto a su vez nos indica que los 10-bits (0-1023) de menor peso en la salida del Amplificador HX711 representan una zona de inexactitud que tiene la celda.

Como ultimo punto a mencionar es que el modulo HX711 suministra a la celda 4.2V y no los 5.0V utilizado en nuestro calculo, esto debido a la caída de voltaje en el Emisor-Colector del transistor PNP que controla al amplificador, por lo tanto el voltaje final a máxima capacidad de carga sera solo 4.15mV y por esta razón el resultado en la salida del Amplificador será algo inferior a los valores calculados previamente. 
    Peso (gr)    Voltaje (mV)    Salida (24-bit)
    5000             4.20mV        1761607  ->  1AE147h
    1000             0.84mV         352320  ->  056040h
     100            0.084mV          35232  ->  0089A0h
      10           0.0084mV           3523  ->  000DC3h
       1          0.00084mV            352  ->  000160h
Tabla 2. Relación de salida por unidades de peso en gramos.
 
De todas maneras esto no afectara el resultado final gracias a un procedimiento de calibración que utilizaremos para registrar un valor de referencia o peso conocido que se utilizara para calcular el peso real, lo importante en todo caso es que la celda mantenga una linealidad entre la fuerza aplicada y el voltaje de salida.


3. Esquema del circuito PIC16F887
La siguiente imagen describe en forma resumida el esquema de conexión para la celda, el amplificador y el microcontrolador utilizado en el ejemplo. El PIC16F887 se alimentara con 5V y la programación se efectuara vía ICSP, un LED conectado al pin RE2 destellara para indicar que el programa esta funcionando, un pulsador BUT conectado al pin RB2 servirá para iniciar el procedimiento de calibración y el pin RC6 corresponde a la salida TXD del modulo USART. La información del peso se enviara en un mensaje al ordenador utilizando un convertidor serial TTL/USB.

Fig9. Circuito para Medición de Peso


4. Programa de Lectura

Ahora crearemos el programa de lectura para el PIC16F887, y para esto necesitamos revisar la señalización de salida que tiene el amplificador HX711 que es relativamente simple de acuerdo a los siguientes pasos:
  • El HX711 coloca el pin DOUT en nivel bajo para indicar que hay datos listos para enviar.
  • El PIC16 detecta si hay disponibilidad de datos verificando la linea DOUT.
  • Para iniciar la lectura el PIC16 enviará mínimo 25 pulsos de reloj, y entre cada pulso realiza la lectura de nivel de la linea DOUT, almacenando estos valores en un registro de 24bits.
  • Opcionalmente el PIC puede enviar un o dos pulsos para cambiar de canal o ganancia en la siguiente lectura.


Fig10. Señalización serial

El programa descrito a continuación muestra el uso de la función de lectura del canal A con ganancia 128 para el modulo HX711, esta función llamada HXReadvalue devuelve un valor absoluto.

#pragma config FOSC = INTRC_NOCLKOUT
#pragma config WDTE = OFF, LVP = OFF
#include <stdio.h>
#include <xc.h>
#define _XTAL_FREQ 8000000 //Frecuencia 8MHz
#define HX_DATpin PORTBbits.RB1 //Pin de salida DOUT
#define HX_SCKpin PORTBbits.RB0  //Pin de pulsos CLK
#define LEDpin    PORTEbits.RE2 //Pin de salida LED
unsigned long adcval = 0; //Registro de 32 bits
unsigned long HXReadvalue(); //Prototipo de función
void main()
{
    OSCCONbits.IRCF = 0b111; //Oscilador a 8MHz Tcy 0.5u
    while(!OSCCONbits.HTS); //Espera hasta estabilizar INTOSC
    ANSEL = 0;    //Deshabilita los pines Analógicos ANS0-ANS7
    ANSELH = 0; //Deshabilita los pines Analógicos ANS8-ANS13
    TRISEbits.TRISE2 = 0; //Salida pin LED
    TRISBbits.TRISB0 = 0; //Pin CLK de salida
    TRISBbits.TRISB1 = 1; //Pin DOUT como entrada
    //OPTION_REGbits.nRBPU = 0; //Activa las Pullup del PORTB
    TXSTAbits.BRGH = 1;    //USART en modo de alta velocidad
    BAUDCTLbits.BRG16 = 0; //Generador de Baudios de 8-bit
    SPBRG = 52;    //Fosc/(16x(9600+1))
    TXSTAbits.TXEN = 1; //Habilita el transmisor
    RCSTAbits.SPEN = 1; //Habilita el modulo USART
    while(1)
    {
        adcval = HXReadvalue();
        adcval &= 0x00FFFFFF; //Enmascara los 24bits
        printf("VAL:%lu\r\n", adcval); //Muestra el valor
        __delay_ms(1000);
        LEDpin = !LEDpin; //Destello LED
    }
}

unsigned long HXReadvalue()
{
    unsigned long value = 0;
    char i = 0;
    HX_SCKpin = 0;
    while(HX_DATpin); //Espera disponibilidad
    for(i = 0; i < 24; i ++) //Solo 25 pulsos Canal A
    {
        HX_SCKpin = 1;
        value <<= 1; //1uS retardo
        HX_SCKpin = 0;
        if(HX_DATpin) //Lectura del pin DOUT
            value ++; //Coloca 1 al bit lsb
    }
    HX_SCKpin = 1;
    value ^= 0x800000; //Convierte a un valor absoluto
    HX_SCKpin = 0;
    return value;
}

Al correr este programa y conectado a una terminal podemos observar que los datos recibidos presentan fluctuaciones en los ultimo dígitos, y por ello es preferible trabajar con promedios por intervalo de tiempo.
Para no efectuar cambios mayores a este programa y debido que el modulo que utilizo tiene el pin RATE conectado a tierra, lo cual me dice que los datos estarán disponibles cada 0.1 segundo(10Hz), siendo necesario al menos 10 segundos para efectuar un promedio de 100 lecturas, adicionare una linea al programa principal para descartar los últimos 4 bits, y así obtener un valor mas estable. Además considerando que la relación de bits por gramo calculado es de 419, el máximo error provocado si la diferencia fuera 15(4-bits), sera 0.038 gramos. 

Fig11. Resultado de lectura de celda

Con la linea adicional el bucle indefinido del programa principal queda de la siguiente manera.

    while(1)
    {
        adcval = HXReadvalue();
        adcval &= 0x00FFFFFF; //Enmascara los 24bits
        adcval = adcval >> 4; //Descarta los últimos 4 bits
        printf("VAL:%lu\r\n", adcval); //Muestra el valor
        __delay_ms(1000);
        LEDpin = !LEDpin; //Destello LED
    }

Fig12. Resultado de celda sin los últimos 4 bits

En este punto lo que necesitamos para que el PIC pueda determinar y mostrar el peso en gramos es agregar un algoritmo que determine la diferencia entre una lectura sin peso y otra con un peso conocido, esta diferencia servirá para efectuar un calculo final aplicando una regla de tres. Un ejemplo para las lecturas de la figura anterior, tomando en cuenta los valores que mas se repiten sera: El valor de referencia sin peso Dref = 531356, y la diferencia cuando se utiliza un peso de 350 gramos es Dif = 539550 - Dref = 8194, entonces para determinar un nuevo peso aplico la siguiente regla Peso = (Lectura - Dref) * 350 / Dif.

Ahora modificamos el programa con estos valores de referencia calculados para obtener las siguientes lecturas de la imagen inferior.

while(1)
{
    adcval = HXReadvalue();
    adcval &= 0x00FFFFFF; //Enmascara los 24bits
    adcval = adcval >> 4; //Descarta los últimos 4 bits
    if(adcval > 531356) //No debe ser menor a la Dref
    adcval = (adcval - 531356) * 350 / 8194; //Formula
    else adcval = 0;
    printf("VAL:%lu\r\n", adcval); //Muestra el valor
    __delay_ms(1000);
    LEDpin = !LEDpin; //Destello LED
}

Fig13. Lecturas de peso en gramos

Se observa que los cálculos realizados por el programa ya ajustado a las referencias medidas, muestran una variación con los pesos reales indicados en la imagen. Esto se debe a que utilize unos envases de conserva que no tienen una medida exacta, incluso ensayando dos de ellos al azar, uno indica 141 gramos y el otro 152 gramos, por lo tanto para efectuar un ajuste real debes utilizar pesos de referencia exactos. 

Fig14. Balanza de prueba


5. Programa de Calibración

Es necesario crear un procedimiento de calibración que permita registrar el valor de salida que tiene la celda sin peso alguno y luego registrar el valor de salida con un peso conocido para así determinar la variación resultante, luego esta información debería almacenarse en una memoria no volátil para evitar repetir el proceso cada vez que el microcontrolador se reinicie, la calibracion solo es necesario cuando la celda es reemplazada o cuando la variación de temperatura sea considerable, para darnos una idea sobre como afecta la temperatura revisando la ficha técnica de esta celda, indica que error por efecto de la temperatura es 0.05% F.S./10°C, lo que significa que la variación por cada 10°C sera de 2.5 gramos(0.05/100) * 5000).
 
Si el peso de referencia es 140g, la ecuación de calculo analizado anteriormente resulta en Peso (gramos) = (Lectura - Dref) * 140 / Dif.

El código de ejemplo para este procedimiento realiza la calibracion utilizando el pulsador BUT en tres pasos:

paso 1. Cuando se presiona el pulsador por al menos un segundo,  se inicia el procedimiento de calibracion y se mantiene en modo espera indicando por mensaje que se libere de peso la balanza
paso 2. Con la balanza libre de peso, se presiona nuevamente el pulsador para que el procedimiento registre el valor de la celda Dref y luego envié otro mensaje solicitando colocar un peso conocido, en nuestro caso 140 gramos.
paso 3. Ya con el peso de referencia sobre la balanza, se presiona nuevamente el pulsador para que el procedimiento registre el valor de la celda y calcula la diferencia Diff = Valor con peso - Dref.
 
Con la finalidad de que el código escrito hasta el momento no sufra cambios en su lógica, es que las lineas de código necesarias para este procedimiento de ajuste se adicionan al bucle principal utilizando retardos indefinidos, con el único fin de mostrar su funcionamiento.  Para una implementación real es recomendable apoyar este procedimiento con interrupciones o técnicas de programación mas robustas como ser una maquina estados finitos. 

Entonces al bucle del programa principal adicionaremos la detección del pulsador, que en caso de ser presionado llamara al procedimiento de Ajuste


while(1)
{
    adcval = HXReadvalue();
    adcval &= 0x00FFFFFF; //Enmascara los 24bits
    adcval = adcval >> 4; //Descarta los últimos 4 bits
    if(adcval > Dref) //No debe ser menor a la Dref
    adcval = (adcval - Dref) * 140 / Diff; //Formula
    else adcval = 0;
    printf("VAL:%lu\r\n", adcval); //Muestra el valor
    __delay_ms(1000);
    LEDpin = !LEDpin; //Destello LED
    if(BUTpin == 0) //Primer pulso de boton
    {
        delay_ms(50);
        if(BUTpin == 0)
        {
            while(BUTpin);
            Ajuste(); //llama al procedimiento de ajuste
        }
    }
}



El procedimiento de ajuste bloqueara la secuencia del bucle principal con su propio bucle el cual finalizara solo cuando el pulsador es presionado por tercera vez.


void Ajuste() //Procedimiento para mostrar
{
    char paso = 0, loop = 1;
    printf("Retire Peso y presione BUT\r\n"); //Muestra el valor
    while(loop)
    {
        adcval = HXReadvalue();
        adcval &= 0x00FFFFFF; //Max 8000000h set MSB to 0
        adcval >>= 4;
        printf("ADC:%lu\r\n", adcval);
        __delay_ms(1000);
        if(BUTpin == 0) //Segundo pulso de botón
        {
            __delay_ms(50);
            if(BUTpin == 0) //Tercer pulso de botón.
            {
                while(BUTpin);
                if(paso == 1)
                {
                    Diff = adcval - Dref;
                    printf("Ajuste final %06u", Diff);
                    loop = 0; //Finaliza el bucle del procedimiento
                }
                if(paso == 0)
                {
                    Dref = adcval;
                    paso ++;
                    printf("Coloque Peso y presione BUT\r\n");
                }
            }
        }
    }
}

Compilamos y cargamos nuevamente el código al microcontrolador para efectuar una prueba de funcionamiento, logrando el resultado que se ilustra en la siguiente imagen.
Fig15. Resultados de una calibracion.



6. Interfaz visual con QT

Esta parte es opcional solo para demostrar como crear una aplicación de escritorio que visualice la información del peso en gramos con un formato mas legible y elegante. Se da por entendido que ya cuentas con el conocimiento y experiencia en la programación orientada a objetos con C++. Para crear la esta aplicación de Escritorio utilizare la plataforma de código abierto  QT(Community), con el IDE QTCreator instalado en el sistema operativo Linux-Debían 11.

El primer paso una vez iniciado QTCreator, es crear un proyecto para escritorio(Qt Widgets Applicaction), en el ejemplo se coloca el nombre HX711_PICMonitor. Ya con el proyecto creado y configurado se adicionara al formulario visual mainwindow.ui los objetos visuales que formaran parte de la interfaz. La siguiente figura muestra los elementos utilizados en la aplicación.
Fig16. Aplicación de Lectura MonitorHX711

Para establecer la comunicación y recibir los datos serie del PIC, QT cuenta con la clase QSerial y debemos incluirlo en el proyecto adicionando a la linea QT += serialport en el archivo de proyecto HX711_PICMonitor.pro, Luego de esto solo resta trabajar con los archivos del programa principal que son: mainwindows.cpp y mainwindows.h
 
 //Archivo mainwindows.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    port = new QSerialPort();
    connect(port, SIGNAL(readyRead()), this, SLOT(readSerialPort()));
    foreach(const QSerialPortInfo &portinfo, QSerialPortInfo::availablePorts())
    {
        ui->comboBox->addItem(portinfo.systemLocation());
    }
}

MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::readSerialPort()
{
    QByteArray databytes;
    if(port->canReadLine()) //Espera el siguiente Texto VAL:347\r\n
    {
        databytes = port->readLine();
        QString msg(databytes);
        msg.remove(0,4); //Quita el texto "VAL:"
        ui->lcdNumber->display(msg.toInt()); //Muestra el valor
    }
}

void MainWindow::on_checkBox_stateChanged(int arg1)
{
    if(ui->checkBox->isChecked())
    {
        port->setPortName(ui->comboBox->currentText());
        port->setBaudRate(port->Baud9600, port->Input);
        port->setFlowControl(port->NoFlowControl);
        if(port->open(QIODevice::ReadOnly))
            ui->statusbar->showMessage("Puerto Conectado", 0);
        else
            ui->statusbar->showMessage("Error de conexion", 0)
    }
    else
    {
        if(port->isOpen())
       {
            port->close();
            ui->statusbar->showMessage("Puerto Desconectado", 5000);
        }
    }
}
 
    //Archivo mainwindows.h
    #include <QMainWindow>
    #include <QSerialPort>
    #include <QSerialPortInfo>
    QT_BEGIN_NAMESPACE
    namespace Ui { class MainWindow; }
    QT_END_NAMESPACE
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
        public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
        private slots:
        void readSerialPort();
        void on_checkBox_stateChanged(int arg1);
        private:
        Ui::MainWindow *ui;
        QSerialPort *port;
    };
 
En la siguiente imagen se muestra como la aplicacion Qt informa del peso en gramos que envia el microcontrolador.
Fig17. Aplicación de Lectura MonitorHX711
 
 
7. Recomendación y Conclusión

Es recomendable utilizar una celda que tenga un capacidad superior a los limites que utilizo en el diseño. por ejemplo si considero que el limite de carga es 100Kg, utilice una celda de 150Kg. Caso contrario reduzca el limite de carga máxima. 

También mencionar que si bien el valor referencia Dref medido cada dia, cada hora o cuando se apaga y enciende el circuito puede variar considerablemente, la relación Diff se mantendrá, una opción interesante para evitar estas variaciones es hacer que el proceso de lectura inicie siempre con la celda libre de carga, para registrar ese valor como la referencia inicial Dref y luego colocar la carga a medir, este lapso no demora mas de un segundo.

Aquí dejo los enlaces para que puedas descargar los proyectos creados con MPLAB y QT para este blog, como usuario de linux comentar que ambos proyectos están comprimidos en gzip y es probable que necesites modificar algunas referencias si quieres compilar en Windows.
Proyecto MPLABX hx711p16f
Proyecto QT5 HX711_PICMonitor

Sin mas que mencionar agradezco tu visita al blog y espero que el ejemplo visto pueda ser útil en tu formación y el proyecto que desarrollas.
Atte. Pablo Zárate Arancibia
email: pablinza@me.com / pablinzte@gmail.com
@pablinzar
Santa Cruz de la Sierra - Bolivia

Referencias Adicionales.

jueves, 4 de junio de 2020

1-WIRE Lectura de temperatura con DS18B20

Sensor DS18B20 con PIC16F


Fig1. DS18B20 (a) KT-040 y (b) Punta de prueba
 

Un gran saludo a los visitantes de este blog, creado para compartir el mutuo interés en la electrónica y en especial la programación de microcontroladores PIC, antes de empezar quiero recordarte que estoy atento a cualquier sugerencia o critica constructiva relacionada con esta publicación, dicho esto al final de esta entrada esta los mis datos de contacto.

En esta ocasión veremos como utilizar uno o mas sensores de temperatura DS18B20 con nuestro microcontrolador PIC16F887, tanto el programa asi como el esquema del circuito que utilizare se encuentran disponibles en mi repositorio https://github.com/pablinza/piclab2.

La programación del microcontrolador PIC se llevara a cabo utilizando el software  MPLABX  y el compilador C  XC8. Ambas aplicaciones están disponibles en la pagina oficial de microchip en sus versiones gratuitas. Aquí dejo los enlaces de las versiones utilizadas para nuestro ejemplo:  <<MPLABX v6.20>>   <<XC8 v2.45>>

Mencionar que una adecuada comprensión de los códigos descritos, es aconsejable poseer conocimientos básicos de la programación en lenguaje C y microcontroladores PIC16F.

Introducción al Tema

Explicaremos brevemente dos aspectos que involucran el proceso de medir la temperatura con el microcontrolador, estos son el protocolo 1-Wire y el sensor DS18B20.

Protocolo 1-Wire: Es un protocolo de comunicaciones serial asíncrono que utiliza un esquema de conexión punto a multipunto, donde hay un maestro (host) que se comunica con el resto de dispositivos (esclavos). Un aspecto interesante de este protocolo es que utiliza sola una linea de comunicación, este canal bidireccional usualmente requiere adicionar una resistencia pull-up, asegurar un nivel logico alto en el bus, cuando esta en modo inactivo, en la figura 2 se muestran las diferentes topologías de conexión que pueden implementarse con este protocolo. para que todos los dispositivos conectados utilicen el tercer estado como mecanismo para liberar el bus. 1-Wire puede implementarse en una topología lineal, fraccionada o estrella.


Fig2. Topología linea, fraccionada y estrella

La implementación de una red 1-wire presenta limitaciones similares a cualquier otro protocolo, y tiene que ver por ejemplo con la distancia maxima para la transmisión debido a la degradación y retardo de la señal por la resistencia interna del cable, pese a ello podemos encontrar dos modos de operación que son: el modo standar que permite una tasa de hasta 16.3kbps, y el modo overdrive que eleva la velocidad

Con respecto a la señalización utilizada por el protocolo 1-Wire de este sensor, el dispositivo maestro sera nuestro microcontrolador PIC y los dispositivos esclavos serán los sensores DS18B20, tome las referencias publicadas en los documentos SPMA057C y AN1119 con el siguiente detalle:

Reset Slot: Es una señal obligatoria que emite el maestro antes de enviar los comandos para comunicarse con un sensor, como vera en la figura 3, el maestro solo el bus en nivel bajo por 480us, y luego libera el bus en nivel alto, entonces en los siguientes 240us un dispositivo esclavo debe responder colocando el bus a nivel bajo, con lo cual el maestro detectara la presencia de un dispositivo activo en el bus.


Fig3. Señal (Reset Slot)

Write Slot 0:  Esta es la señalización que emite el maestro para enviar un bit 0 al dispositivo esclavo, observe la figura 4 donde, el maestro coloca el bus en nivel bajo por 60us suficientes para que el dispositivo esclavo realice la lectura del nivel bajo (bit 0).

Fig4. Señal (Write Slot 0)

Write Slot 1: Esta es la señalización que emite el maestro para enviar un bit 1 al dispositivo esclavo, observe la figura 5 donde, el maestro coloca el bus en nivel bajo por máximo 15us, para luego libera el bus en nivel alto hasta completar los 60us, entonces el dispositivo esclavo realizara la lectura del nivel alto (bit 1) a partir de los 15us.

Fig4. Señal (Write Slot 1)

Read Slot 0: Con esta señalización el maestro recibe desde el dispositivo esclavo un bit0. Para la lectura el maestro coloca el bus en nivel bajo por un tiempo de 5 a 15us y luego lo libera en nivel alto, entonces el dispositivo esclavo contestara colocando el bus en nivel bajo (bit 0), por lo que el maestro debe hacer lectura del nivel antes de los 60us que dura esta señal, observe la figura 5.

Fig5. Señal (Read Slot 0)

Read Slot 1: Esta señal permite al maestro recibir un bit0 desde el dispositivo esclavo. Para la lectura el maestro coloca el bus en nivel bajo por un tiempo de 5 a 15us y luego lo libera en nivel alto, entonces el dispositivo esclavo contestara manteniendo el bus liberado en nivel alto (bit 1), por lo que el maestro debe hacer lectura del nivel antes de los 60us que dura esta señal, observe la figura 6.

Fig6. Señal (Read Slot 1)

El tiempo mínimo de espera o tiempo de recuperación por cada bit transmitido por el bus 1-Wire es de al menos 10us, pero refiérase a las especificaciones del dispositivo esclavo para un dato mas preciso.
Sensor de Temperatura DS18B20: Describiré un breve resumen acerca de este sensor tomando como referencia la hoja de datos DS18B20 del fabricante Maxim Integrated.

El ds18b20 esta disponible en los encapsulados TO-92, SOIC y SOP, opera con voltaje de alimentación de 3.3 - 5.5V y un consumo de 1.5mA en su modo activo.

Se trata de un sensor digital de temperatura con una resolución ajustable de 9 a 12-bit, con un rango de operación que va desde los -55°C a 125°C. 

El sensor tiene la ventaja de ser un dispositivo direccional gracias a que utiliza el protocolo serial asíncrono 1-Wire, donde cada sensor tiene asignado en su memoria ROM un código único de 64-bit para su identificación.

Se sugiere revisar la hoja de datos para conocer mas detalles de los tiempos mínimos y máximos establecidos para la señalización del bus.

La conexión del sensor se puede realizar de dos maneras, una de ellas se denomina parasite-powered que no trataremos en esta publicación, y el otro modo es el external-power supply, utilizaremos la segunda opción llevando a cabo las conexiones del sensor como se observa en la figura 7.

Fig7. Conexión del Sensor

Como notara en el esquema de conexión, se necesita una resistencia externa de 4.7k en modo pull-up para garantizar un nivel logico alto, cuando el bus es liberado. La secuencia de señales que requiere el microcontrolador (maestro) para comunicarse con el sensor (esclavo) se muestra en la figura 8 y es la siguiente:

Fig8. Secuencia de comunicación

Inicialización: El maestro emite el Reset Slot, y espera respuesta de un esclavo

Direccionamiento: El maestro emite el comando ROM para la identificación de un esclavo, siendo las posibles opciones, los siguientes comandos:

  • Search ROM[0xF0]: Permite que el maestro inicie una procedo de lectura secuencial de los códigos ROM cada sensor conectado al bus
  • Read ROM[0x33]: Cuando existe un solo dispositivo esclavo conectado en el bus, este comando permite leer su código ROM.
  • Match ROM[0x55]: Cuando se conoce el código ROM del dispositivo esclavo, este comando permite al maestro seleccionar al dispositivo esclavo del resto de dispositivos conectados.
  • Skip ROM[0xCC]: Permite la selección todos los dispositivos conectados con las que se realizara una operación común, por ejemplo que todos los sensores inicien el proceso de conversión.
  • Alarm Search[0xEC]: Permite al maestro identificar los sensor que se encuentran con estado de alarma.

Función: Una vez establecida la identificación (Direccionamiento) con un dispositivo esclavo,  el maestro enviara un comando de función para el sensor DS18B20, las posibles opciones son:

  • ConverT[0x44]: El sensor iniciara el proceso de conversion que se completara después de  un tiempo mínimo (Tiempo de Conversión). Este tiempo depende de la resolución del sensor, siendo los valores mínimos 94ms a 9-bit, 188ms a 10-bit, 375ms a 11-bit y 750ms a 12-bit(defecto).

  • Read Scratchpad[0xBE]: El scratchpad hace referencia al conjunto de 8 registros o bytes que contienen informacion del sensor, entre las cuales están el valor de temperatura en grados centígrados y configuracion del sensor tal como se observa en la figura 9.

Fig9. Registros del Sensor(Scracthpad)
  • Write Scratchpad[0x4E]: El comando permite escribir tres bytes de configuracion que son accesibles a la memoria EEPROM, estos bytes corresponden a los registros 2, 3 y 4 del Scratchpad. 

  • Copy Sractchpad[0x48]: Almacena el valor de los registros 2, 3 y 4 del Scratchpad en la memoria no volátil EEPROM.

  • Recall E2[0xB8]: Recupera desde la memoria no volátil EEPROM los valores de configuracion para los registros 2, 3 y 4 del Scracthpad.

  • Read Power Supply[0xB4]: No se detalla en el blog, ya que esta relacionado con el modo de operación parasite-powered.

Ahora detallaremos el formato de algunos registros del Scratchpad, empezando por los dos primeros que contienen el resultado de una conversion, observe que en la figura 10, se muestra que el bit11 al bit15 tiene repetida la indicación del signo, luego los bit4 al bit10 representa la parte decimal del resultado y finalmente los bit0 al bit3 es la fracción del resultado.

Fig10. Formato del Resultado

Observe que la resolución de bits utilizada afecta únicamente a la fracción del resultado, por ejemplo si utilizamos la resolución por defecto que es 12-bit, haremos uso de los cuatro bit0 al bit3, logrando una precisión con incrementos de 1/16 = 0.0625 °C en la fracción, en cambio si utilizamos 9-bit, haremos uso solo del bit3, logrando incrementos de 1/2 = 0.5 °C

El registro 4 que se muestra en la figura 11, corresponde al byte de configuración donde se encuentran los bits R1 y R0, estos permiten ajustar la resolución del sensor, siendo la combinación del par R[1:0]=11 para 12-bit, R[1:0]=10 para 11-bit, R[1:0]=01 para 10-bit y [R1:0]=00 para 9-bit.

Fig11. Registro de configuracion (12-bit defecto)

Esquema del circuito PIC

La propuesta como ejemplo de uso para el sensor ds18b se ilustra en el esquema del circuito de la figura 12, en el esquema no se muestran las conexiones relacionadas con la alimentación de 5V y tampoco los componentes mínimos necesarios para el funcionamiento del microcontrolador. 

Fig12. Esquema del Circuito PIC

El microcontrolador utilizara el oscilador interno configurado a 8MHZ para proveer un ciclo de instrucción de tan solo 0.5us, notara que todos los sensores están conectados de forma simultanea al pin RB0 del PIC sin la resistencia pull-up 4.7k, esto es así porque activaremos la resistencia interna que poseen los pines del PORTB en el microcontrolador.  El pulsador identificado como BUT1, servirá para indicar en el programa que se realice la lectura del código de identificación ROM del sensor conectado al bus para que pueda enviarse como mensaje a través del puerto serie.  

En funcionamiento normal, los datos de temperatura de cada sensor se enviaran como mensaje serial a intervalos de un segundo.

Programa del PIC16F887

Para poder explicar el programa del microcontrolador, es importante conocer el esquema de programación por estados y la ejecución concurrente de tareas que utilizo en la mayoría de los ejemplos de mi blog. Para lo cual te sugiero puedas repasar lo descrito en las siguientes entradas:

<Programación por Estados>

<TRM0 Configuración Usos>

Utilizaremos los procedimientos y funciones de la librería "ds18b20.c" y nos enfocaremos en dos de las tres tareas que se ejecutan en el programa principal cada 1ms, donde la tarea taskLED1() tiene como propósito únicamente destellar el diodo LED del pin RE2 para indicar que el Microcontrolador opera con normalidad y las otras dos tareas son:

taskBUT1(): Esta tarea espera que el pulsador del pin RB1 se mantenga presionado por intervalo mínimo de 200ms para iniciar la lectura del código ROM de un sensor. La idea de este procedimiento es utilizarlo para identificar de forma física cada sensor y elaborar una lista de todos los sensores que se consideraran en el código principal para la lectura de temperatura, la lista final de los tres sensores para nuestro ejemplo seria:

uint8_t dev0rom[8]={0x28,0xFF,0x97,0xD8,0x61,0x16,0x04,0x9A};  //Sensor 1
uint8_t dev1rom[8]={0x28,0xFF,0xA0,0xD5,0x61,0x16,0x04,0xD2}; //Sensor 2
uint8_t dev2rom[8]={0x28,0xFF,0x5C,0x8A,0x61,0x16,0x03,0x94}; //Sensor 3

Para completar la lista anterior con el pulsador debemos asegurarnos que un solo sensor este conectado al bus, ya que el mensaje recibido por el puerto serial corresponde a este único sensor, luego no restaría anotar el código que tiene un formato hexadecimal, tal como se observa en la figura 13.

Fig13. Mensaje Serial para lectura de códigos ROM

La codificación de este procedimiento es el siguiente:

void taskBUT1(void)
{
    static uint8_t state = 0, cnt;
    switch(state)
    {
        case 0:
//Detecta nivel bajo en pulsador
            if(BUT1pin == 0)
            {
                if(cnt++ > 200)
//Pulsador presionado min 200ms
                    state = 1;
            } else cnt = 0;
            break;
        case 1:
//Lectura del código ROM sensor
            INTCONbits.GIE=0;
            if(DS18Reset())
            {
                DS18ReadROM(dev0rom); /
/Comando para lectura ROM
                printf("ROM CODE:");
                printHEX(dev0rom[0]);
                printHEX(dev0rom[1]);
                printHEX(dev0rom[2]);
                printHEX(dev0rom[3]);
                printHEX(dev0rom[4]);
                printHEX(dev0rom[5]);
                printHEX(dev0rom[6]);
                printHEX(dev0rom[7]);
                printf("\n");
            }
            INTCONbits.GIE=1;
            cnt = 0;
            state++;
            break;
        case 2:
//Espera liberación del pulsador
            if(BUT1pin)
            {
                if(cnt++ > 200)
//Pulsador liberado min 200ms
                    state = 0;
            } else cnt = 0;
    }
}

taskDS18(): Es la tarea principal que ejecuta el programa, ya que realizar la lectura secuencial de los tres sensores conectados al bus con intervalos de un segundo, por lo que sera necesario tres segundos para completar un ciclo de lectura de todos los sensores. Este tiempo puede ser menor, sobre todo si consideramos que el tiempo de conversion a resolución de 10-bit es aproximadamente 187ms y existe la posibilidad de ejecutar el comando de función ConverT de forma simultanea en todos los sensores, algo que no se esta aplicando en nuestro ejemplo.

Note que el procedimiento utiliza la variable devi para determinar con que sensor se establecerá la comunicación, esta variable esta condicionada en el ultimo estado. Si desea incrementar la cantidad de sensores también deberá condicionar el rango de esta variable. La figura 14 muestra los resultados que arroja el procedimiento a través del puerto serial UART. 

Fig14. Resultados de medición por UART
 

A continuación se detalla la codificación del procedimiento taskDS18:

void taskDS18(void)
{
    static uint8_t state = 0, devi = 0;
    static uint16_t cnt = 0;
    uint16_t res;
    switch(state)
    {
        case 0:
//Verifica y inicia conversion del sensor
            INTCONbits.GIE = 0;
            if(DS18Reset())
//Si hay presencia de sensor
            {
                if(devi == 0) DS18StartDev(dev0rom);
                if(devi == 1) DS18StartDev(dev1rom);
                if(devi == 2) DS18StartDev(dev2rom);
                cnt = 200;
//TConv 10-bit min 187ms
                state = 1;
            }
            else
            {
                cnt = 1000;
//Espera próxima lectura 1s
                state = 2;
            }
            INTCONbits.GIE = 1;
            break;
        case 1:/
/Lectura del sensor por turno
            if(cnt-- == 0)
//TConv 10-bit 187
            {
                INTCONbits.GIE = 0;
                if(DS18Reset())
                {
                    if(devi == 0) res=DS18GetValue(dev0rom);
                    if(devi == 1) res=DS18GetValue(dev1rom);
                    if(devi == 2) res=DS18GetValue(dev2rom);
                }
                INTCONbits.GIE = 1;
                temr = (uint8_t) (res & 0x000F);
                temr = (100/16) * temr; //1/16 = 0.0625
                tem = (uint8_t) ((res & 0x7FFF) >> 4);
               
/*Muestra informacion de temperatura*/
                putch('T');
                printByte(devi);
                putch(':');
                printByte(tem);
                putch('.');
                if(temr < 10) putch('0');
//Padding 0
                printByte(temr);
                putch('\n');
                if(devi++ >= 2) devi = 0;
                cnt = 800;
//Espera 800ms
                state = 2;
//Next state to wait
            }
            break;
        case 2:
//Intervalo de espera entre intentos
            if(cnt-- == 0) state = 0;
            break;
    }
}

El programa completo y funcional desarrollado con MPLABX lo encontraras en el siguiente enlace a mi repositorio Git: <https://github.com/pablinza/piclab2>

También dejo este vídeo que resume la programación de PIC y las pruebas realizadas durante el laboratorio.


Conclusiones y Recomendaciones

Las pruebas efectuadas en nuestro programa nos muestran el funcionamiento adecuado de este sensor, considerando las buenas prestaciones, su bajo coste y gran disponibilidad en las electrónicas locales, lo convierten en una excelente opción para muchas aplicaciones de medición. Seguidamente se listan algunas recomendaciones a considerar si vas a implementar un proyecto con este sensor:

  • Los tiempos de requerimiento utilizan retardos provisto por el compilador que fueron levemente ajustados a la frecuencias de 8MHz, en caso de usar otra frecuencia, deberá  efectuar cambios en la definicion _XTAL_FREQ o en todo caso hacer uso de temporizadores para conseguir los retardos.

  • Debido a que los requerimientos de tiempo para validar los datos son fijos, es conveniente evitar cualquier interrupción una vez iniciada la comunicación, si el programa hace uso de interrupciones deberá considerar una correcta aplicación de los procedimientos y funciones de este proyecto.

  • El estado inactivo del bus siempre sera un nivel alto, si por alguna razón la comunicación es suspendida, se debe asegurar que el bus permanezca en su estado inactivo, porque de otra forma un nivel bajo mayor a 120us provocara el reinicio del bus en los dispositivos esclavos.

  • Puede reducir el tiempo de espera en la conversión si su aplicación no requiere de la precisión establecida por defecto de 12-bit(0.0625°C) lo que representa un mínimo de 750ms, entonces configurando a 9-bit(0.5°C) se podría reducir a tan solo 94ms.

Sin mas que mencionar agradezco tu visita al blog y espero que el ejemplo visto pueda ser útil en tu formación y el proyecto que desarrollas.

Atentamente, Pablo Zárate Arancibia  email: pablinza@me.com / pablinzte@gmail.com, @pablinzar

Santa Cruz de la Sierra - Bolivia