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 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 es 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 incorpora un amplificador de ganancia programable PGA y un convertidor ADC de 24 bits de dos canales, y lo interesante es que su diseño permite conectar directamente a la celda de carga, reduciendo gran parte del circuito necesario para que un microcontrolador pueda medir el peso. El circuito esta disponible únicamente en formato SOP de 16 pines.


Fig6. Circuito HX711

Por fortuna este circuito circuito integrado es muy comercial y puede adquirirse como un modulo listo para ser integrado a nuestro sistema digital. 

Fig7. Modulo HX711

Alguno detalles adicionales del 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 ±0.5V/Ganancia
  • Salida serial sincronizada
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. Ajuste de Canal y PGA

Como el rango de voltaje a escala completa para la conversión es ±0.5V/Ganancia, el rango final sera de ±80mV si la ganancia es 32, ±40mV si la ganancia es 64 y ±20mV con una ganancia de 128. El valor digital en salida esta en formato complemento a dos en el rango de 800000h a 7FFFFFh.

Si alimentamos la celda con 5V y esta tiene una salida de 1.0mV/V, el voltaje final a máxima capacidad de carga sera 5mV, para trabajar esta variación conviene utilizar el canal A con ganancia 128 porque el rango de entrada para la conversión seria ±20mV, y la relación de voltaje por bit se calcula en Rv =0.040mV/2^24bits. Con esta configuración si aplicamos el peso máximo de 5kg a la celda, su salida llegara a medir 4.15mV y el resultado digital de la conversión puede calcularse dividiendo este valor con la relación V/bits, esto es +0.005mV / Rv = 2097152 o 200000h, tomar en cuenta que este resultado estará en formato complemento a dos y para obtener un valor absoluto debemos sumar 800000h.

Otro dato que podemos calcular es la relación de bits por unidad de peso, para eso utilizare el gramo como unidad de medida donde 5000gr representa 0.005mV en la salida, y el voltaje para 1gr se calcula en 0.005mV x 1gr / 5000gr = 0.000001mV. Entonces la relación de bits por unidad es 0.000001mV / Rv = 41.9 bits, es decir que por cada gramo de peso adicional el resultado digital se incrementa con 42 unidades.

Luego notara que en la practica el voltaje que aplica el modulo HX711 es 4.15V debido en parte a la caída de voltaje del transistor PNP que alimenta a la celda, por lo tanto el voltaje final a máxima capacidad sera solo 4.15mV y por esta razón las lecturas que recibe el PIC16F887 serán de magnitud inferior a los valores calculados, lo bueno es que este punto tampoco no afectara al calculo final del peso debido que un procedimiento de calibración ajustara el valor de referencia y luego se utilizara unicamente la variación, siendo el mayor requisito la linealidad del sensor.

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 enviá 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 41.9, el máximo error provocado si la diferencia fuera 15(4-bits), sera 0.5 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 este punto, revisando la característica de la celda que utilizo me indica que el efecto de 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 mantendra, una opcion 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 demoria 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




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, hoy les quiero mostrar como realizar una medición de temperatura utilizando el conocido sensor digital DS18B.

El objetivo de esta sección es leer la temperatura del sensor DS18B conectado al microcontrolador PIC16F887, y enviar su valor en grados centígrados por el puerto serie UART.

La programación se realizara utilizando el compilador XC8 y el entorno MPLABX disponibles en la pagina de microchip de forma gratuita, ademas también necesitara de los siguientes archivos peripheral.h y peripheral.c que contienen procedimientos y funciones básicas para el PIC16F887, puede prescindir de ellos o incluso modificarlos a su criterio si posee conocimientos sobre los registros SFR y sus respectivo bits.

1. El protocolo 1-Wire.

1-Wire es un protocolo de comunicaciones en serie que implementa un bus maestro esclavo con una sola linea o señal de comunicación, la linea requiere una resistencia pull-up para que todos los dispositivos conectados utilicen el tercer estado como mecanismo para liberar el bus. 1-Wire puede implementarse en una topologia lineal, fraccionada o estrella.



Las limitaciones con respecto a la distancia máxima de transmisión se dan por las reflexiones de onda, el retardo, y la degradación producida por la resistencia del cable, siendo unos 200 metros el valor aproximado de un cable convencional.
En cuanto a la velocidad se tiene dos modos de operación: el modo standard con una tasa de 16.3kbit/s y el modo overdrive que eleva la transferencias hasta diez veces mas.

Con respecto a la señalización tome como referencia los documentos SPMA057C y AN1119 para distinguir los siguientes modos:

Secuencia de reinicio: Permite reiniciar el bus en cada dispositivo esclavo preparándolos para iniciar la comunicación.


El dispositivo maestro coloca el bus en nivel bajo por 480us, luego lo libera y espera una respuesta durante los siguientes 240us. En caso de no existir un dispositivo esclavo conectado al bus, la linea permanecerá en nivel alto.

Escritura del bit 0: Permite enviar un bit 0 a los dispositivos esclavos

El dispositivo maestro coloca el bus en nivel bajo por 60us para que el dispositivo esclavo realice la lectura del nivel bajo(bit 0).

Escritura del bit 1: Permite enviar un bit 1 a los dispositivos esclavos


El dispositivo maestro coloca el bus en nivel bajo por un tiempo entre 5 a 15us y luego lo libera hasta completar 60us, durante este tiempo el esclavo deberá realizar la lectura del nivel alto(bit 1).
Lectura del bit 0: Permite recibir un bit 0 del dispositivo esclavo

El dispositivo maestro coloca el bus en nivel bajo por un tiempo entre 5 a 15us y luego lo libera, entonces el dispositivo esclavo coloca nuevamente el bus en nivel bajo(bit 0) para que el maestro realice la lectura, el nivel bajo se mantendrá hasta completar un tiempo total de 60us.

Lectura del bit 1: Permite recibir un bit 1 del dispositivo esclavo


El dispositivo maestro coloca el bus en nivel bajo por un tiempo entre 5 a 15us y luego lo libera, entonces el dispositivo esclavo coloca el bus en nivel alto(bit 1) para que el maestro realice la lectura, el nivel alto se mantendrá hasta completar un tiempo total de 60us.

Para todos los casos el bus deberá permanecer en nivel alto por al menos 10us antes iniciar la escritura o lectura de un siguiente bit.


2. Sensor de temperatura DS18B

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 un voltaje de alimentación de 3.3 - 5.5V y un consumo de 1.5mA en modo activo.
Se trata de un sensor digital de temperatura con una resolución ajustable desde los 9-bit hasta los 12-bit con las que podemos medir temperaturas en el rango de -55°C a 125°C. Ademas tiene la ventaja de ser un dispositivo direccional gracias a que implementa una comunicación utilizando el protocolo 1-Wire, y donde cada sensor tiene almacenado en su memoria ROM un código de 64-bit para la identificación.
Se sugiere revisar la hoja de datos que muestra los tiempos mínimos y máximos establecidos para la señalización del bus, para así enfocarnos unicamente en describir los comandos y registros necesarios para realizar la lectura con el PIC.
La conexión del sensor como se observa en la siguiente figura es muy simple, el único requerimiento adicional sera agregar una resistencia pull-up(recomendado 4.7k) para mantener un nivel alto en el bus en modo inactivo, incluso puede emplear un pin del PIC activando la resistencia pull-up interna(aprox. 10k).


Establecido el bus y considerando que existe al menos un dispositivo esclavo(sensor ds18b20) conectado podemos efectuar la comunicación en una secuencia de tres pasos descritos a continuación.
Inicializacion: El maestro establece una secuencia de reinicio para detectar la presencia de uno o mas dispositivos conectados al bus.
Direccionamiento: El maestro enviá un comando ROM para identificar a cada dispositivo esclavo o detectar si algún sensor se encuentra en condición de alarma(no se abordara este punto en el blog), el ds18b20 reconoce los siguientes comandos ROM:
  • Search ROM[0xF0]: Este comando permite al maestro leer los códigos  de identificación de todos los esclavos conectados al bus, esto se lleva a cabo mediante una secuencia de discriminación que no se explicara en este blog, el tiempo total necesario para llevar a cabo todo el proceso dependerá de la cantidad de dispositivos que tenga el bus.
  • Read ROM[0x33]: Cuando solo existe un dispositivo esclavo en el bus, este comando permite al maestro leer su código de identificación.
  • Match ROM[0x55]: Cuando se conoce el código de identificación de un dispositivo esclavo, este comando permite seleccionar o preparar al dispositivo en particular para realizar una operación, el resto de dispositivos conectados quedaran a la espera de un reinicio en el bus.
  • Skip ROM[0xCC]: Este comando permite al maestro seleccionar a todos los dispositivos cuando se requiere realizar una operación en común, por ejemplo es posible dar una sola orden a todos los sensores para que inicien el proceso de conversión que tiene una demora de hasta 750ms y de esta manera se tengan los datos listos para su lectura.
  • Alarm Search[0xEC]: Permite al maestro detectar si existe un sensor en estado de alarma.
Función: Una vez seleccionado(Direccionamiento) el o los dispositivos esclavos,  el maestro enviá un comando de función para realizar una operación especifica, el ds18b20 reconoce los siguientes comando de función.
  • ConverT[0x44]: Esta orden permite a los sensores conectados al bus iniciar el proceso de conversión, luego finaliza la comunicación. los datos de temperatura estarán listos para su lectura en un tiempo mínimo de: 94ms(9-bit), 188ms(10-bit), 375ms(11-bit) y 750ms(12-bit).
  • Read Scratchpad[0xBE]: El scratchpad es la información compuesta por un grupo de 8 registros mas un byte de verificación CRC, tal como se observa en la siguiente imagen:

El comando de función Read Scratchpad sirve para que el maestro pueda leer los 64-bits de información que contiene el Scractchpad de un determinado sensor y de esta manera conocer los valores de temperatura y configuración actual. Mas adelante explicaremos en detalle el formato de los registros y los valores establecidos por defecto
  • Write Scratchpad[0x4E]: El comando permite que el maestro escriba tres bytes de información correspondientes a los registros 2, 3 y 4 del Scratchpad. En la comunicación se enviá primero el bit LSB.
  • Copy Sractchpad[0x48]: Este comando sirve para que el contenido de los 2, 3 y 4 del Scratchpad se guarden en una memoria EEPROM.
  • Recall E2[0xB8]: El uso de este comando no se detalla en el blog, pero esta relacionado con el uso de las alarmas del sensor
  • Read Power Supply[0xB4]: El uso de este comando no se detalla en el blog, pero esta relacionado con el uso de energía parásita en el bus.
Ahora detallaremos el formato de los registros que contiene el Scratchpad, solo haremos mención a los que aplicaremos en el programa presentado en blog.

Registro 0(byte de menor peso) y 1(byte de mayor peso) conforman la información de temperatura que tiene el sensor luego de haber finalizado el proceso de conversión el par de registros forman un dato entero con signo de 16-bits de los cuales 12-bits corresponden a la magnitud y los bit S indican el signo.


Si el sensor esta configurado a una resolución de 11-bits, el bit 0 de este numero se descarta, si la resolución es de 10-bits se descartan los bits 0 y 1, y en caso de que la resolución sea 9-bits se descartan los bits 0,1 y 2. por esta razón la precisión de la temperatura va desde los 0.5°C(9-bit) hasta los 0.0625°C(12-bit).

El registro 4 corresponde al byte de configuración donde se encuentran los bits R1 y R0, estos permiten ajustar la resolución 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.



Por defecto el valor de los bits son R1=1 y R0=1(12-bit).

3. Diagrama de circuito
La propuesta como ejemplo de uso para el sensor ds18b se detalla en el siguiente diagrama de circuito, mismo que no incluye detalles de la alimentación y elementos relacionados con el circuito de reinicio.


4. Procedimiento de lectura
El procedimiento a seguir para una correcta lectura del sensor se ilustra en el siguiente diagrama, resaltando que el cuarto paso es dependiente del comando de función enviado por el maestro; por ejemplo si el comando de dirección es Read ROM[0x33], el maestro debe recibir los 64-bit de identificación enviados por el sensor sin necesidad de enviar un comando de función. En cambio una vez seleccionado el sensor, si el maestro enviá un comando Read Scratchpad[0xBE], el maestro recibe los ocho registros de información relacionada con la temperatura y configuración, en este punto también es posible obviar el cuarto paso en caso de un comando de función ConvertT[0x44].


Utilizaremos un par de ficheros que contienen tres funciones básicas para emular la operación del protocolo 1-wire con el sensor DS18B, estos ficheros se encuentran en el siguiente enlace ds18b20.h y ds18b20.c. Te recomiendo puedas  revisar su contenido para conocer como las instrucciones se acomodan a los requerimientos de tiempo que necesita el bus, y porque el uso de estas funciones en en nuestro programa nos abstrae de tocar los aspectos relativos a la señalización. 
A continuación se describe el programa principal que contiene la definición de los pines utilizados y la inclusión del fichero ds18b20.h, así mismo se activara la resistencia pull-up para cumplir el requerimiento del bus con la resistencia adicional, la secuencia principal del código es llamar al procedimiento taskDS18 de forma continua.

#pragma config FOSC=INTRC_NOCLKOUT, WDTE=OFF, LVP=OFF
#define _XTAL_FREQ 8000000
#include "peripheral.h"
#define DS18pin PORTBbits.RB2 //Define el pin donde se conecta el sensor
#define DS18tris TRISBbits.TRISB2
#include "ds18b20.h"
char romcode[8];
char value;
int rawdata;
void main()
{
    OSCSetup(); //Ajusta el oscilador interno a la definición _XTAL_FREQ
    ANSEL = 0; //Desactiva los pines analógicos AN0-AN7
    ANSELH = 0; //Desactiva los pines analógicos AN8-AN13
    USARTSetup(9600);
    TRISEbits.TRISE2 = 0; //Salida para el LED1
    EnablePU(); //Activas las resistencias pull-up del PORTB
    while(1)
    {
        taskDS18();
    }
}

Ahora abordaremos el procedimiento taskDS18 y para ello vamos a considerar dos posibles situaciones al momento de implementar el circuito.
Situación #1: Si solo va utilizar un sensor, entonces no sera necesario conocer su código de identificación, utilizando el comando Skip ROM[0xCC] se enviá directamente el comando de función deseado.

taskDS18()
{
    DS18Reset())
    DS18Write(SKIPROM);
    DS18Write(CONVERT); //Iniciar la conversion
    __delay_ms(800); //Espera minima de la conversion
    DS18Reset();
    DS18Write(SKIPROM);
    DS18Write(READSC); //Lectura del Scratchpad
    value = DS18Read(); //Lectura Registro 0 Byte LSB
    rawdata = DS18Read(); //Lectura Registro 1 Byte MSB
    rawdata <<= 8; //Se desplaza el byte MSB en la variable
    rawdata |= value; //Se conforman ambos bytes MSB+LSB
    printf("RAW:%04X ", rawdata); //Se imprime el valor de 16-bits
    value = rawdata & 0x000f; //Enmascara los bits de precision
    rawdata = rawdata >> 4; //Desplaza el registro de temperatura
    printf("T:%3d.%u\r\n", rawdata, value * 6); //Muestra la temperatura
    __delay_ms(200);
}


Situación #2: Si hay mas de un sensor conectado al bus, entonces es necesario conocer el código de identificación de cada uno, en este caso el comando Search ROM[0xF0] tiene por finalidad registrar la identificación de cada sensor, pero no describiré su uso en este momento, pero si podríamos conectar un sensor a la vez y mediante el comando Read ROM[0x33] leer los códigos de cada uno para luego trabajarlos con su debida identificación.

Procedimiento para lectura del código de identificación de un sensor


taskDS18()
{
    DS18Reset(); DS18Write(READROM);
    romcode[0] = DS18Read(); //Get Family Code 0x28
    romcode[1] = DS18Read(); //48-bit LSB code
    romcode[2] = DS18Read();
    romcode[3] = DS18Read();
    romcode[4] = DS18Read();
    romcode[5] = DS18Read(); //48-bit MSB code
    romcode[6] = DS18Read();
    romcode[7] = DS18Read(); //CRC
    printf("ROM CODE:");
    for(value = 0; value < 9; value++)
    printf("%02X ", romcode[value]);
    printf("\r\n");
}


Procedimiento de lectura de un sensor a través de su identificación ejemplo: 28 FF 5C 8A 61 16 03 94.

taskDS18()
{
    DS18Reset();
    DS18Write(MATCHROM);
    DS18Write(0x28);
    DS18Write(0xFF);
    DS18Write(0x5C);
    DS18Write(0x8A);
    DS18Write(0x61);
    DS18Write(0x16);
    DS18Write(0x03);
    DS18Write(0x94);
    DS18Write(CONVERT);
    __delay_ms(800); //Espera de conversion
    DS18Reset();
    DS18Write(MATCHROM);
    DS18Write(0x28);
    DS18Write(0xFF);
    DS18Write(0x5C);
    DS18Write(0x8A);
    DS18Write(0x61);
    DS18Write(0x16);
    DS18Write(0x03);
    DS18Write(0x94);
    DS18Write(READSC);
    value = DS18Read();
    rawdata = DS18Read();
    rawdata <<= 8;
    rawdata |= value;
    printf("RAW:%04X ", rawdata);
    value = rawdata & 0x000f;
    rawdata = rawdata >> 4;
    printf("T:%3d.%u\r\n", rawdata, value * 6);
    __delay_ms(200);

}


5. Conclusiones

Las pruebas efectuadas en nuestro programa nos muestran un adecuado funcionamiento del sensor, las buenas prestaciones junto a lo accesible que resulta adquirir este dispositivo en las electrónicas locales, lo convierten en una excelente opción para muchas aplicaciones de control.

Los tiempos de requerimiento utilizan retardos provisto por el compilador que fueron levemente ajustados a la frecuencias de 8MHz, en caso de frecuencias menores a 4MHz podría ser necesario efectuar cambios debido a que el ciclo de maquina se incrementa a mas de 1uS, una solución mas eficiente podría pasar por utilizar unos de los temporizadores TMRx del PIC para generar los retardos, pero esto claramente va depender de la disponibilidad del recurso.  
 
Debido a que los requerimientos de temporalización 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 las funciones descritas en este blog agregando la gestión interrupciones por cambio y/o temporizadores.

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. para realizar este cambio debe enviar el comando de función Write_Scratchpad[0x4E] seguido de 3 bytes en la que el tercer byte representa el registro de configuración con los bits R1 y R0 según el requerimiento deseado.

Pablo Zarate Arancibia
Ingeniero Electrónico

pablinzte@gmail.com
pablinza@me.com