viernes, 26 de julio de 2019

USB Serial CDC con PIC16F

Comunicación Serial USB-CDC con PIC16F



Muchas gracias por tu visita a este blog sobre programación de micro-controladores PIC, en esta ocasión quiero compartir un conocimiento respecto al uso de la comunicación serial con la clase CDC del protocolo USB. Como bien saben al día de hoy prácticamente los puertos serial RS232 no están presentes en los ordenadores, ni mucho menos en móviles y tables, porque todo ha sido reemplazado por la comunicación USB, pero este cambio no ha hecho que la comunicación serial asíncrona a niveles TTL/CMOS se pierda y mas todavía si hablamos del mundo de los sistemas embebidos y microcontroladores donde todavía son muy utilizados.

Actualmente existen una infinidad de dispositivos convertidores TTL/RS232 al protocolo USB, y básicamente emulan la comunicación serie en nuestro ordenador asignando un puerto COMxx(Windows) o ttyUSBxx(Linux).

Desde el punto de vista del diseñador electrónico, para obtener conectividad USB en el microcontrolador sera necesario agregar al circuito un chip convertidor como el CP210x, FT232, CH340, PL230x, etc, y así establecer una comunicación segura y transparente con el ordenador. Otra alternativa es buscar un microcontrolador que incluya el hardware necesario para implementar el protocolo en su memoria.

El objetivo de esta entrada es poder implementar la comunicación serial con el ordenador sin hacer uso de un convertir externo, puesto que existen muchos microcontroladores PIC que poseen los recursos para soportar la pila USB haremos uso de un modelo en particular correspondiente a la serie PIC16F creando un programa para enviar información de estado de una entrada digitale y una analógica al ordenador, en el ordenador veremos estos mensajes utilizando una aplicación terminal. También a manera de complemento crearemos una pequeña aplicación en Qt para visualizar los datos USB.


La programación se realizara utilizando el compilador XC8, el entorno de desarrollo MPLABX y la utilidad MMC(Microchip Code Configurator), todo disponible de forma gratuita en la pagina de microchip. Ahí les paso los links oficiales para la descarga de las versiones que utilizo en el ejemplo:

  • XC8 8-bit C Compiler 2.10, para Windows y para Linux
  • Entorno IDE MPLABX 5.20, para Windows y para Linux
  • MMC 3.95, En el siguiente Link explica las formas de instalar
1. Protocolo USB y la Clase CDC


El protocolo USB(Universal Serial Bus) nació con la idea de reemplazar la gran cantidad de conectores relacionados con las computadoras personales, simplificando la conexión con un mayor ancho de banda para la transferencia de datos. Actualmente existen 5 revisiones: USB1.0(1996), USB1.1(1998), USB2.0(2000), USB3.0(2008), USB4.0(2019) que van desde los 12Mbps hasta los 40Gbps.

El protocolo implementa una topologia en estrella con la posibilidad de conectar hasta 127 dispositivos administrados por un solo host, en algunos casos el rol de host se negocia entre dos dispositivos mediante un protocolo conocido como OTG.

El host(Computadora) efectúa la mayor parte de la actividad que requiere el protocolo, normalmente el sistema operativo del ordenador carga los drivers necesarios en forma dinámica cuando se conecta un dispositivo al bus(PIC), y es identificado mediante el par PID(Product ID) / VID(Vendor ID).

USB soporta cuatro modos de transferencia: Control, Interrupción(Interrupt), Masiva(Bulk) e Isócrona(Isochronous), y distribuye la alimentación de 5V a todos los dispositivos sin necesidad de una fuente externa, esto siempre y cuando el dispositivo sea de bajo consumo, aqui la limitacion va de acuerdo a la versión: 500mA(USB2.0) y 900mA(USB3.0).

Los niveles lógicos se representan por la diferencia de al menos 200mV entre las lineas D+ y D-, siendo 1 lógico cuando D- es mayor a D+ y 0 lógico cuando D- es menor a D+. A diferencia de RS‐232 o interfaces serie similares donde el formato de los datos no están definidos, en USB existen varias capas del protocolo que definen el tamaño de los paquetes que se enviaran.

En este punto quiero mencionar que la comprensión de los detalles de este protocolo con respecto a su funcionamiento van mucho mas allá de lo que explica este blog. Si realmente estas interesado en conocer con mayor profundidad este extenso protocolo te recomiendo dar lectura a la documentación oficial de grupo USB-IF que promueve y brinda soporte al estándar.

Como nuestra finalidad es comunicar una computadora(Host) con el PIC(Device), desde el lado del PIC trabajaremos con la utilidad MCC(Mplab Code Configurator) que nos librara de entrar en los detalles que ocurren en niveles inferiores del protocolo.

Una clase USB determina la funcionalidad que tendrá un dispositivo cuando se conecta al bus, hay una lista de clases ya definidas que pueden implementarse en un microcontrolador, y mencionaremos dos que pueden servir para nuestro caso.
  • CDC(Communication Device Class): Utilizado para establecer una comunicación emulando el comportamiento de un puerto serial(RS232, TTL, etc).
  • HID(Human Interface Class): Esta clase permite conectar dispositivos de entrada(ratón, teclado, impresora, etc) a la computadora y simplificar el proceso de instalación.
Optaremos por utilizar CDC y mas propiamente la subclase ACM(Abstract Control Layer) que brinda mejor soporte de dispositivos y la mayoría de los sistemas operativos incorporan su funcionalidad sin necesidad de utilizar archivos(drivers) adicionales. En Windows un dispositivo CDC conectado aparece como un puerto serie COMx, y en linux se crea el vinculo raíz /dev/ttyACMx.

2. Microcontrolador PIC16F145x

Actualmente microchip ofrece un amplio numero de microcontroladores que soportan la comunicación USB, en todas las gamas de 8, 16 y 32 bits. En particular he optado en utilizar el PIC16F1455 que junto al PIC16F1454 son de los microcontroladores que menos componentes externos requieren para funcionar y ademas su disponibilidad en encapsulado de 14-pines me hacen pensar en lo simple y económico que resulta la implementación del protocolo USB.
Veamos un resumen de las características de este PIC que es relativamente nuevo y forma parte de los productos que utilizan la tecnología XLP(Xtreme Low Power).

- Voltaje de operación 2.3V a 5.5V
- Frecuencia máxima 48MHz
- Memoria de programa FLASH 14Kbytes
- Memoria de datos RAM 1Kbytes
- Memoria de datos EEPROM 128bytes
- Puertos USB compatible para V2.0
- Módulos TMR0/1/2, PWM1/2, MSSP, EUSART

Adicionalmente el PIC16F1455 cuenta con:

- Modulo Comparador 2 canales
- Modulo CWG de 4 fuentes
- Modulo FVR de tres ajustes
- Modulo ADC de 10-bits
- Modulo DAC de 5-bits

Como les comentaba son muchas las cualidades que posee este microcontrolador, y si revisas la hoja de datos encontraras aun mas elementos como ser el sensor de temperatura incorporado.

El circuito utilizado para trabajar con el ejemplo de este blog es muy simple y puede montarse en un protoboard pequeño, el único inconveniente dependiendo de tu país sera contar con el microcontrolador PIC, aquí en mi cuidad la única manera de obtener uno es mediante compra de tiendas en linea como Digi-Key o Mouser.



Al ser un circuito de bajo consumo(menos que 0.5A) este se alimentara directamente del puerto USB, un potenciómetro POT1 se utilizara como entrada analógica, un pulsador BUT1 como entrada digital. El LED1 sera una salida que indica actividad del PIC y el LED2 indicara si esta establecida la comunicación USB.



Una vez montado el circuito en el protoboard, conectamos el programador de PIC(Pickit3) y creamos un proyecto MPLABX con el programa del microcontrolador, para conectar y probar la funcionalidad del puerto USB utilice un cable de teléfono cortado a la mitad, un extremo se conectara a la computadora y el otro directo al protoboard tal como como se ve en la siguiente imagen.


3. Configuración MPLABX y MCC
Ahora llego la hora de trabajar en el programa del PIC, y como primer paso crearemos un proyecto nuevo configurando los recursos a utilizar.
Recuerda que deberás tener instalado en tu PC la aplicación MPLABX y el compilador XC8, así una vez abierto el IDE seleccionas en el menú principal Tools->Plugins->Available Plugins la utilidad MPLAB Code Configurator para que empiece su instalación, asegurate de estar conectado a internet. Luego finalizada la instalación es probable que te pida reiniciar el IDE, y luego cuando vuelves a arrancar podrás verificar en Tools->Plugins->Installed si la utilidad esta instalada.


Aquí creamos un nuevo proyecto seleccionando como dispositivo el PIC16F1455 y el compilador XC8, luego seleccionamos en el menú Tools->Embedded->MPLAB Code Configurator, que nos abrirá una pantalla de asistencia para realizar la configuración inicial de los recursos que se utilizaran en el PIC, básicamente ajustaremos los fusibles, el oscilador y puertos.

La configuración de los fusibles lo hacemos desde la pestaña System Module->Registers, donde desactivamos el circuito de reinicio Reset MLCR seleccionando el pin como una entrada digital


El oscilador se ajusta también en la mista pestaña, con los valores seleccionados trabajaremos con el oscilador integrado a una frecuencia de 48MHz/3 que son 16MHz y un ciclo de instrucción de 0.25uS.


En el visor de pines Pin Manager seleccionamos la configuración de entrada o salida para cada pin, los pines de la comunicación USB DAT+ y DAT- se configuran automáticamente.


Con la pestaña Pin Module, definimos los nombres de cada pin y demas opciones, aquí debemos habilitar la resistencia pull-up del pin RA5 donde se conectara el pulsador BUT1, esta habilitación es individual en cada pin y se necesita también habilitar la opción en todo el puerto con el bit nWPUEN en Module->Register.

.

Ahora buscamos y agregamos los módulos TMR0, ADC y USB de la ventana Device Resources




Una vez agregado revisamos en la lista de periféricos del proyecto y procedemos a configurar cada unos de estos módulos.


Con el modulo TMR0, se busca generar una interrupción cada 1mS que servirá como base de tiempo para el programa principal.



En el modulo ADC ajustamos la referencia 5V y el tiempo de adquisición, para medir la variación de voltaje en la entrada POT1


El modulo USB se basa en una librería MLA modificada para configurar las opciones de la clase CDC, lo único que cambie aquí fue el String del producto.


Finalmente se procede a generar el código con la pestaña Generate, creando toda la estructura lógica de nuestro proyecto, muchos de estos archivos no se tocaran y solo se trabajara en el archivo main.c


4. Programando la comunicación USB
En la estructura del proyecto trabajamos solo con el archivo main.c, para elaborar un programa que realice lo siguiente:
  • Destellar el LED1 cada segundo
  • Indicar a través del LED2 si se estableció la conexión USB
  • Verificar si el pulsador BUT1 es presionado, en caso de ser así enviar un mensaje vía USB con un valor que indique su estado.
  • Leer el voltaje POT1 y si hay una diferencia con un lectura previa, enviar un mensaje vía USB con un valor que indique su magnitud.
El código del programa principal se detalla mas abajo, en el archivo muchas lineas ya fueron insertadas por el configurador MCC cuando se genero el proyecto, por eso adicione unicamente las variables y procedimientos APPTask y USBTask.
  • APPTask es responsable de las entradas BUT1, POT1 y el LED1 haciendo control de los cambios conforme a los procedimientos descritos anteriormente.
  • USBTask es responsable de la conectividad USB con el Host(PC), y dependiendo del estado enviara como mensaje las variables que fueron registradas en APPTask.
    main.c

#include "mcc_generated_files/mcc.h"
#include <string.h>
#include <stdio.h>
static uint8_t writeBuffer[64]; //Declaracion del buffer de salida
volatile char adcOK = 0, butOK = 0; //Banderas de control
volatile char butval, adcval; //Variables de lectura
void APPTasks(); //Prototipo del procedimiento APP
void USBTasks(void); //Prototipo del procedimiento USB
void main(void) //Programa principal
{
    SYSTEM_Initialize(); //Inicializa Puertos, Perifericos, etc
    INTERRUPT_GlobalInterruptEnable(); //Activa las interrupciones
    INTERRUPT_PeripheralInterruptEnable();
    ADC_SelectChannel(POT1); //Selecciona el canal de captura ADC
    TMR0_SetInterruptHandler(APPTasks); //Asigna la tarea ISR cada 1ms
    while(1)
    {
        USBTasks(); //Ajecucion continua del procedimiento USB
    }
}

void APPTasks() //Procedimiento APP cada 1mS via ISR
{
    unsigned int val;
    static unsigned int but1cnt = 0, led1cnt = 0, adc1cnt = 0;
    //Inicio del Control para LED1
    led1cnt ++;
    if(led1cnt > 499) //cada 500ms
    {
        LED1_Toggle(); //Destella led
        led1cnt = 0;
    }

    //Inicio del Control para BUT1
     val = BUT1_GetValue(); //Lectura del pulsador BUT
    if(val != butval) //Solo si BUT cambia de estadoT
    {
         but1cnt ++;
        if(but1cnt > 300) //si mantiene por 300ms
        {
            butval = !butval; //Actuliza estado BUT
            butOK = 1; //Indica cambio de estado BUT
        }
    }
    else but1cnt = 0;

    //Inicio de Control para ADC
    adc1cnt ++;
    if(adc1cnt == 199) ADC_StartConversion(); //inicia en 200ms
    if((adc1cnt > 399) && ADC_IsConversionDone()) //Espera 200ms
    {
         val = (ADC_GetConversionResult() >> 6) / 10; //Lectura valor
        if(val != adcval) //Si valor es diferente
       {
            adcval = val;
            adcOK = 1; //indica cambio de valor POT
        }
        adc1cnt = 0;
    }
}

void USBTasks(void) //Procedimiento que atiende a CDC-USB
{
    if( USBGetDeviceState() < CONFIGURED_STATE ) return;
     LED2_SetHigh(); //Activa LED1 cuendo USB esta configurado
    if(USBUSARTIsTxTrfReady()) //Si el Buffer de salida esta libre
    {
        if(butOK) //Verifica si hay cambio de estado BUT
        {
            butOK = 0;
            sprintf((char *) writeBuffer, "B:%u\n", butval);
            putUSBUSART(writeBuffer,4); //Envia mensaje
        }
        if(adcOK) //Verifica si hay cambio de estado POT
        {
            adcOK = 0;
            sprintf((char *) writeBuffer, "V:%03u\n", adcval);
            putUSBUSART(writeBuffer,6); //Envia mensaje
        }
    }
     CDCTxService(); //Procesa servicio USB
}


Una vez terminada la edición se procede a compilar y cargar el programa al microcontrolador, aquí hago uso de un programador Pickit3

5. Conectado al puerto USB

Ya con el programa cargado en el PIC16F1455, vamos a conectarlo a un puerto USB de la computadora, si todo marcha bien el LED1 estaría destellando y el LED2 se mantendrá encendido. Utilizando el comando lsusb de linux podemos ver como el sistema operativo ha reconocido nuestro dispositivo USB, ver imagen:





En linux todos los dispositivos están enlazados al sistema de archivos en la raiz /dev y el puerto serie de nuestro PIC sera accesible a través de /dev/ttyACMx, esto es similar al puerto COMx creado en Windows. Entonces podemos utilizar un programa terminal para enviar y recibir información del dispositivo serial, yo utilizo una aplicación HTerm que esta disponible de forma gratuita para ambos sistemas operativos. Al conectar esta aplicación con el puerto asignado al PIC nos mostrara en pantalla la siguientes información:



Aquí podemos observar los dos tipos de mensaje que enviá el PIC, uno indica el estado del pulsador BUT (B:0) y otro el resultado de la conversión del POT (V:048), recordar ademas que cada mensaje se enviá solo si hay cambio con respecto al ultimo dato, esto para no enviar la misma información todo el tiempo.

6. Recepción del mensaje en QT

Esta parte del blog es opcional ya que el objetivo de la comunicación PIC - PC ya se cumplió, entonces aquí trabajare en crear una aplicación monitor de los datos que enviá el PIC, donde asumiré que ya se cuenta con el conocimiento y experiencia base en la programación orientada a objetos y C++. Para eso utilizare QT que al ser una cross-platform disponible para diferentes S.O, me posibilita su instalación y funcionamiento en mi pc linux(Debian v11), en particular trabajare con la versión QT5 y el asistente QTCreator 12, donde creare el proyecto CDC_PICMonitor.

Ya con el proyecto creado y configurado adicionare un formulario visual mainwindow.ui en la que insertare los siguientes elementos(objetos visuales):
un checkbox para conectar y desconectar la recepción del puerto, un textEdit para mostrar el valor del BUT y otro textEdit_2 para mostrar el valor del POT, adicional hay unos labels que indican la funcionalidad de cada elemento.

QT cuenta con la clase QSerial y la misma se debe agregar al proyecto adicionando QT += serialport en el archivo de proyecto CDC_PICMonitor.pro, y partir de aquí trabajare solo con los archivos del programa principal que son:

mainwindow.h

#include <QMainWindow>
#include <QtSerialPort>
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(); //Procedimiento que atendera la recepcion
    void on_checkBox_stateChanged(int arg1);
    private:
    Ui::MainWindow *ui;
    QSerialPort *port;
};

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    port = new QSerialPort(); //Instancia a la clase
    connect(port, SIGNAL(readyRead()), this, SLOT(readSerialPort()));
}
MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::readSerialPort() //Cuerpo del Procedimiento
{
    QByteArray databytes;
    if(port->canReadLine()) //Si hay mensaje recibido
    {
        databytes = port->readLine(); //Recibe el mensaje
        QString msg(databytes);
        if(msg.at(0) == 'B') //Si el mensaje es del BUT
        {

            ui->lineEdit_2->setText(msg.remove(0,2)); //Actualiza
        }
        if(msg.at(0) == 'V') //Si el mensaje es del POT
        {
            ui->lineEdit->setText(msg.remove(0,2)); //Actualiza
        }
    }
}
void MainWindow::on_checkBox_stateChanged(int arg1)
{
    if(ui->checkBox->isChecked())
    {
        port->setPortName("/dev/ttyACM0");
        port->setBaudRate(port->Baud9600, port->Input);
        port->setFlowControl(port->NoFlowControl);
        if(port->open(QIODevice::ReadOnly)) //Abre el puerto para lectura
            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);
        }
    }
}

Finalizada la edición y previa compilación de este programa, lo corremos y conectamos nuestro circuito PIC al ordenador, luego hacemos click en conectar para ver como se muestra la información del BUT y POT en las casillas textEdit.



7. Visualización con QML

QML es un lenguaje muy utilizado para crear interfaces graficas y forma parte del framework de QT dando posibilidad de ejecutarlo hasta en un dispositivo móvil. Como un pequeño ejemplo agregaremos a nuestro proyecto CDC_PICMonitor un formulario QML con dos elementos gráficos que nos mostraran la información enviada por el PIC.

Agregamos al proyecto un archivo QML con nombre QMView1.qml y en este archivo en la vista de edición escribimos el siguiente código base.

import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Extras 1.4
Rectangle
{
    id: rectangle1
    x: 0
    y: 0
    width: 500
    height: 300
    color: "#000000"
}

Esto crea un rectangulo de 500 x 300 pixels con fondo negro, sobre este rectangulo en la vista de diseño agregamos un componente Gauge y un Status Indicator, el primero lo utilizaremos para ver la magnitud del POT y el segundo para el estado del BUT, tambien adicionare un texto de referencia para indicar el nombre del programa.


Ya con esto pasare dar una toque personalizado a cada elemento, agregando las siguientes lineas al archivo QMLView1, dejando como resultado lo siguiente:

import QtQuick 2.0
import QtQuick.Controls 2.3
import QtQuick.Extras 1.4
Rectangle
{
    id: rectangle1
    x: 0
    y: 0
    width: 500
    height: 300
    color: "#000000"

    Text {
        id: element
        x: 156
        y: 14
        color: "#ffffff"
        text: qsTr("CDC PIC MONITOR")
        font.bold: false
        fontSizeMode: Text.FixedSize
        font.pixelSize: 18
    }

    Gauge {
        objectName: "pot1"
        id: gauge
        x: 4
        y: 200
        width: 492
        height: 74
        orientation: 1
        maximumValue: 100
        value: 50
    }

    StatusIndicator {
        objectName: "but1"
        id: statusIndicator
        x: 299
        y: 84
        width: 84
        height: 70
        color: "#30ff00"
        active: false
    }
   
    Text {
        id: element1
        x: 308
        y: 63
        width: 66
        height: 15
        color: "#ffffff"
        text: qsTr("PULSADOR")
        font.pixelSize: 12
    }
   
    Text {
        id: element2
        x: 17
        y: 174
        color: "#f4f1f1"
        text: qsTr("ADC PULSADOR")
        font.pixelSize: 12
    }

}


Con esta plantilla basica pasaremos a enlazar los elementos id:"pot1" y "but1" a nuestro proyecto CDC_PICMonitor, adicionaremos las siguientes lineas a los archivos:

CDC_PICMonitor.pro

QT       += core gui serialport quick

mainwindows.h

#include <QQuickView>
private:
    QQuickView *qv1;
    QObject *pot1, *but1;


En el archivo mainwindows.cpp modificaremos el constructor principal y el procedimiento readSerialPort quedando en lo siguiente:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    port = new QSerialPort();
    connect(port, SIGNAL(readyRead()), this, SLOT(readSerialPort()));
    qv1 = new QQuickView();
    qv1->setSource(QUrl::fromLocalFile("../CDC_PICMonitor/QMView1.qml"));
    qv1->show();
    pot1 = qv1->findChild<QObject*>("pot1");
    but1 = qv1->findChild<QObject*>("but1");
}

void MainWindow::readSerialPort()
{
    QByteArray databytes;
    if(port->canReadLine())
    {
        databytes = port->readLine();
        QString msg(databytes);
        if(msg.at(0) == 'B')
        {

            ui->lineEdit_2->setText(msg.remove(0,2));
            if(msg.at(0) == '1') but1->setProperty("active", false);
            else but1->setProperty("active", true);
            return;
        }
        if(msg.at(0) == 'V')
        {
            ui->lineEdit->setText(msg.remove(0,2));
            pot1->setProperty("value", msg.toInt());
        }
    }
}

Luego compilamos y ejecutamos el codigo. Observaremos los valores en los elementos graficos de la ventana QML.


8. Conclusiones

Dejo un video que resume toda la creacion del proyecto en MPABX



Aqui dejo los enlaces para que puedas descargar los proyectos creados con MPLAB y QT para en este blog, como usuario de linux comentar que ambos proyectos estan comprimidos en gzip y es probable que necesites modifcar algunas referencias si quieres compilarlos en Windows:
Proyecto MPLABX cdc16f
Proyecto QT5 CDC_PICMonitor

La utilidad MCC al momento de trabajar este ejemplo implementa solo la clase CDC como un lite statck del protocolo USB, si quieres utilizar otras clases como ser HID, MSC, ADC o incluso una personalizada débes utilizar las librerias MLA, hay bastante informacion con ejemplos en la documentacion de microchip.

Algo interesante de trabajar con la subclase ACM es que la codificación de linea es automatica, gracias a esto el protocolo se ajusta a cualquier velocidad(baudios) de la aplicación PC o terminal.

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

jueves, 6 de junio de 2019

1-WIRE Lectura de Temperatura y Humedad con DHT-11/22

Sensor Humedad/Temperatura DHT con PIC


Muchas gracias por tu visita a este blog relacionado con la programación de micro-controladores PIC, en esta entrada quiero mostrarles como utilizar los sensores de humedad y temperatura DHT que nos sera de gran utilidad en aplicaciones que requieran medir información ambiental o meteorología.

 El objetivo de esta entrada es realizar la lectura adecuada del sensor DHT con un microcontrolador PIC16F, para lo cual se implementara la comunicación con el protocolo 1-Wire respetando las especificaciones técnicas del sensor.

La programación se realizara utilizando el compilador XC8 y el entorno de desarrollo MPLABX, ambos disponibles de forma gratuita en la pagina de microchip.
Paso 1.Sobre el Sensor DHT

Es un sensor que mide Humedad y Temperatura de forma simultanea, cuenta con un procesador interno que realiza la medición al elemento capacitivo y termistor integrado, proporcionando el resultado a través de una señal digital. El proceso de comunicación con el PIC utiliza un único pin gracias al protocolo 1-Wire. Describiremos dos modelos identificados como DHT11 y DHT22 que poseen un encapsulado y disposición similar pero con las siguientes diferencias  técnicas:

  • DHT11: Trabaja con un rango de medición de temperatura de 0 a 50 °C con precisión de ±2.0 y un rango de humedad de 20% a 90% RH con precisión de 4%. Los ciclos de lectura debe ser como mínimo 1 o 2 segundos.
  • DHT22: El rango de  medición de temperatura es de  -40°C a 80 °C con precisión de ±0.5 y rango de humedad de 0 a 100% RH con precisión de 2%, el tiempo entre lecturas debe ser de 2 segundos.
A simple vista se puede observar que la diferencia con respecto a la medición esta en el rango y la precisión, pero revisando con mas detalle las especificaciones el pulso de inicio 1-Wire utiliza diferentes tiempos y el resultado se representa en un formato diferente, además el DHT11 soportar ciclos mas rápidos de lectura.


Paso 2. Conectar el Modulo al PIC16F.

El voltaje de operación nominal es 5V, la salida requiere utilizar una resistencia pull-up(entre 1-5k) para asegurar los niveles lógicos con el Microcontrolador, el manual también recomienda agregar un capacitor de 100nF entre VDD y GND para un mejor filtrado de la señal.


Es posible utilizar cualquier pin digital del PIC, eso si debe asegurarse de que sea bidireccional, algo que también debemos considerar es la distancia a la que puede estar el sensor del cual no tengo un dato exacto del limite máximo, pero he realizado pruebas sin inconvenientes hasta una distancia de 30mts, y considero que a mayores distancias se deberá utilizar un resistencia pull-up mínima(1k) considerando la perdida de voltaje en las lineas y posibles interferencias que puedan afectar a la señal de los datos.

Paso 3. Programación y lectura del sensor.

A fin de facilitar la rápida implementación del modulo, se crearon procedimientos en el archivo dht.c incluidos en la carpeta del proyecto MPLABX, en la ultima sección veras el enlace de descarga. 
El programa principal debe incluir las siguientes lineas que permiten definir el tipo y los pines utilizados con este sensor.
 
#define _XTAL_FREQ 4000000
#define DHTTYPE 11 // Modelo de sensor
#define DHTpin PORTBbits.RB0  //Pin de comunicación DTH
#define DHTtris TRISBbits.TRISB0 //Bit de control
#include "dht.h"

Para establecer la comunicación bidireccional utilizando solo un conductor, el sensor utiliza una variante del protocolo 1-Wire que establece reglas de temporización para las señales lógicas del microcontrolador.
 
En cada señal de lectura(Start signal) que realiza el microcontrolador, el sensor envía una señal de respuesta(Response signal) seguidos de una trama de 40bits, la duración de esta trama es variable en función de los niveles lógicos de cada bit recibido. 
Normalmente el pin del bus se encuentra en estado alto, entonces el (Start signal) que establece el microcontrolador es un pulso a nivel bajo con una duración de al menos 1ms(DHT22) o 18ms(DHT11) antes de normalizar el bus(nivel alto). Observe el diagrama de tiempos.
 
El sensor como respuesta(Response signal) colocara el bus en nivel bajo por 80us para luego normalizar esperando otros 80us, partir de este momento el sensor empezara a enviar una trama de 40 bits con la siguiente regla de tiempos:
(Bit 0) Nivel bajo por 50us y normalizar por un tiempo entre 25-28us.
(Bit 1) Nivel bajo por 50us y normalizar por un tiempo de 70us.
 
El siguiente código muestra un ejemplo simple para dar inicio al proceso de lectura en el sensor, siguiendo el diagrama de señal que se muestra en la parte superior.

    DHTtris = 0;    //Modo salida
    DHTpin = 0;     //Inicia el pulso bajo
    if(DHTTYPE == 22)
      __delay_ms(4); //Mantiene mínimo 1ms
    if(DHTTYPE == 11)
      __delay_ms(18);//Mantiene mínimo 18ms
    DHTpin = 1;     //Coloca el bus en nivel alto
    __delay_us(20); //Espera mínimo 20uS
    DHTtris = 1;    //Modo entrada
    while(DHTpin);  //Espera pulso bajo(hasta 40uS)
    while(!DHTpin); //Espera pulso alto(hasta 80uS)
    while(DHTpin);  //Espera pulso bajo(hasta 80uS)

 
Para mejorar este procedimiento inicial se deberá considerar limitar el tiempo de espera dentro de cada bucle indefinido, para esto se puede crear un procedimiento que utilice una variable de control que permita determinar la cantidad de tiempo transcurrido durante la espera.

De los 40 bits recibidos del sensor los dos bytes mas significativos representan la medición de humedad, los siguientes dos bytes corresponden a la medición de temperatura, finalmente un ultimo byte se utiliza para la comprobación de errores que se calcula con la suma de todos los bytes recibidos.


El sensor DHT22 representa la medición como un dato entero de 16-bits y como puede medir temperaturas negativas se utiliza el bit mas significativo del tercer byte para indicar el signo.

Ahora mostraremos la lista de los procedimientos que podemos utilizar para hacer uso de este sensor:
  • DHTSetup(): Es un procedimiento simple que coloca el pin de datos como entrada manteniendo un nivel lógico alto en el bus 1-Wire
  • DHTUpdate(dhtstruct *dht): Esta función realiza la lectura de las mediciones que posee el sensor y retorna un valor del tipo char para indicar si el proceso se realizo correctamente(1) o se produjo un error(0), ademas puesto que la lectura contiene información de humedad y temperatura se utiliza como parámetro un puntero a una estructura ya definida en el encabezado.
         typedef struct
         {
           unsigned int rawhum; //16bits para la humedad
           unsigned int rawtem; //16bits para la Temperatura
         } dhtstruct;




Paso 4. Prueba y Simulación.

Ahora escribiremos un pequeño programa para poner en practica y demostrar el uso de este sensor utilizando los procedimientos previamente descritos.

El siguiente código describe un programa que realiza la lectura de un sensor DHT11, muestra el resultado por el puerto USART en su formato de bits original y su conversión final.
  
#pragma config FOSC = INTRC_NOCLKOUT, WDTE = OFF, LVP = OFF
#include <xc.h>
#include <stdio.h>
#define _XTAL_FREQ 4000000
#define DHTTYPE 11  //Modelo DHT11
#define DHTpin PORTBbits.RB0 //Pin de datos RB0
#define DHTtris TRISBbits.TRISB0
#include "dht.h"
dhtstruct dht; //instancia a la estructura dht
unsigned int humedad, temperatura;
void main(void)
{
    char res;
    ANSEL = 0;  //Desactiva las entradas analógicas AN0-AN7
    ANSELH = 0; //Desactiva las entradas analógicas AN7-AN13
    OSCCONbits.IRCF = 6; //Configura el intosc a 4MHz Tcy = 0.5uS
    while(!OSCCONbits.HTS); //Espera estabilidad del oscilador
    DHTSetup();//Configura el pin de lectura para el DHT
   
    //CONFIGURAICON DEL MODULO USART 9600 BAUDIOS
    TXSTAbits.BRGH = 1; //UART en modo alta velocidad
    BAUDCTLbits.BRG16 = 1; //Generador de baudios en modo 16-bits
    SPBRG = 0x67; //X = _XTAL_FREQ/(4*BAUD) - 1;
    SPBRGH = 0x00;//X=[4000000/(4*9600)]-1 = 103 = 0x0067
    TXSTAbits.TXEN = 1; //Habilita la transmisión
    RCSTAbits.CREN = 1; //Habilita la recepción
    RCSTAbits.SPEN = 1; //Habilita el modulo USART
    while(1)
    {
        res = DHTUpdate(&dht); //Inicia la lectura del sensor
        if(res) //Verifica si se realizo correctamente
        {
            humedad = dht.rawhum >> 8; //Lectura la parte entera de la humedad
            temperatura = dht.rawtem >> 8; //Lectura la parte entera de la temp
            printf("Raw Hu=%04X Te=%04X\r\n", dht.rawhum, dht.rawtem);
            printf("Final Hu=%02u Te=%02u\r\n", humedad, temperatura);
        }
        __delay_ms(3000);
    }
}


1. Valores recibidos por la terminal USART


2. Ensayo del programa con un PIC16F886



Paso 5. Conclusiones y Recomendaciones.


Luego de hacer la demostración del programa podemos concluir que podemos implementar fácilmente cualquiera de estos dos sensores.

Para la conversión de lectura del sensor DHT22 deberá hacer una división entre 10, para conseguir el valor entero y si desea precisión con decimales debe guardar el resultado en variables del tipo float.
  float humedad, temperatura;
  humedad = dht.rawhum / 10.0F;
  temperatura = dht.rawtem / 10.0F;
  printf("Raw Hu=%f Te=%f\r\n", humedad, temperatura);
 
Los procedimientos descritos han sido probados a frecuencias igual o superiores a 4MHz en las versiones Free y Pro del compilador XC8; si requiere trabajar con frecuencias menores a 4MHz sera necesario modificar la función DHTReadbyte del archivo dht.c.

Una desventaja con estos sensores es la velocidad de las lecturas y el tiempo de espera para realizar una lectura nueva(2 segundos), aunque en muchos casos no es un factor negativo si consideramos que la Temperatura y Humedad son variables que no cambian de forma rápida.
 
Aquí dejo el enlace para que puedas descargar el proyecto creado en MPLABX para este blog, 
 
Enlace para Descarga -> DHTPIC16F 

Esperando que este ejemplo pueda servirte de ayuda agradezco tu visita al blog, atentamente Pablo Zárate
contacto:pablinza@me.com / pablinzte@gmail.com @pablinzar
Santa Cruz de la Sierra - Bolivia

 

miércoles, 5 de junio de 2019

I2C Utilizar un LCD con PCF8574

Modulo PCF8574x con el PIC16F

Antes que nada quiero agradecer tu visita a este blog relacionado con la programación de micro-controladores PIC, En esta ocasión veremos como utilizar la tan conocida pantalla LCD utilizando únicamente dos lineas para datos gracias al protocolo serial I2C y el chip PCF8574x.

El objetivo de esta sección enviar mensajes a la pantalla LCD utilizando las dos lineas de comunicación serial MSSP que posee el PIC16F887.

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.
 
Al finalizar la sección les dejare el enlace para descargar la carpeta del proyecto que contiene todos los archivos citados.

Modulo PCF8574x con el PIC16F
 
Para establecer una comunicación serial con el chip PCF8574xlos microncontroladores PIC cuentan con el modulo MSSP(Master Sync Serial Port),  el cual posibilita implementar dos protocolos muy utilizados que son SPI(Serial Peripheral Interface) e I2C(Inter-Integrated Circuit). En nuestro programa utilizaremos los archivos i2c.h y i2c.c que poseen las funciones y procedimientos necesarios del protocolo I2C, y tambiénlos archivos pcf8574.h y pcf8574.c que incluyen los procedimientos necesarios para comunicarnos con el chip PCF8754.


Paso 1. Sobre el Modulo PCF8574x.

I2C es un protocolo serial desarrollado por Phillips Semiconductors en la década de los 80, y fue concebido principalmente para comunicar dispositivos que están montados en un mismo sistema o circuito impreso.



El chip PCF8574 fabricado por Texas Instrument es un expansor paralelo de 8 bits controlado por I2C que opera a una velocidad estándar de 100KHz, diseñados para proveer monitoreo y control serial de forma fácil y económica. Existen dos variantes de este chip que son el PCF8574 y PCF8574A que se diferencian únicamente por el rango o mapa de dirección que pueden utilizar conforme a los valores lógicos en sus pines A2:A0.
 

Una gran ventaja de este protocolo es que establece una interfaz bidireccional maestro esclavo de hasta 128 dispositivos, aunque en la practica cada dispositivo limita su rango de asignación a un determinado mapa que puede ser configurado de forma externa o mediante instrucciones sobre el mismo protocolo.



Implementar el bus I2C con el chip PCF es bastante simple, si observa la figura solo se requiere utilizar resistencias pull-up por cada pin del microcontrolador, establecer la dirección de cada dispositivo conectado y su respectiva alimentación.

Ahora si lo que se necesita es controlar una pantalla LCD sera necesario implementar el siguiente esquema de circuito.
 


Algo interesante es que este circuito esta disponible como un modulo genérico producido para aplicaciones de prototipado rápido, y goza de popularidad popular por su bajo costo económico($us. 3), incluso planteando comprar los elementos por separados no se justifica el precio por el trabajo y tiempo necesario para el montaje. Salvo que se trate de un producto final.

 

Por defecto este modulo tiene las lineas A2:A0 con nivel lógico alto que define una dirección de 27h (PCF8574) y 3Fh(PCF8574A); Si requiere agregar mas dispositivos o modificar la dirección necesitara soldar o conectar las siguientes lineas o pads en el circuito.


Otro detalle es que el modulo ya cuenta con las resistencias pull-up requeridas por la interfaz y no sera necesario agregar estas resistencias incluso si utiliza otros dispositivos o sensores I2C.


Paso 2. Conectar el Modulo al PIC16F.



Solo necesitara identificar y conectar las cuatro lineas que posee el modulo, dos corresponden a la alimentación(5V/GND), y dos para la comunicación SDA(datos) y SCL(reloj). Muchas pantallas LCD cuentan con una luz de retroiluminacion(Backlight) que puede ser controlador a través de la interfaz siempre que coloque un puente(jumper) en el conector J2. 
 
El modulo también posee un potenciómetro que permite ajustar el contraste que la pantalla, deberá establecer un valor adecuado al momento de ensayar el programa.


Paso 3. Programación y envió de mensajes.

A fin de facilitar la rápida implementación del modulo en nuestra aplicación, se crearon los siguientes procedimientos y funciones incluidas en el archivo pcf8574.h y pcf8574.c. Se deberá incluir la cabecera y las siguientes definiciones al inicio del programa:

  #include "peripheral.h"
  #include "i2c.h"
  #define PCFADDR 0b01001110 //Dirección 27h bit0=R/W
  #include "pcf8574.h"


El campo de dirección PCFADDR dependerá de los valores lógicos en los pines A2:A0 del chip PCF8574, y se obtendrá conforme al siguiente formato de direccionamiento I2C.
 1. Para un Modulo PCF8574A

 2. Para un Modulo PCF8574

Ahora mostraremos la lista de los procedimientos que podemos utilizar para enviar mensajes a nuestra pantalla LCD.
  • PCFSetupLCD(char mode): Inicializa el modulo y la pantalla, como parámetro obligatorio deberá especificar el modo de operación en LINES1(una linea) o LINES2(multi-linea) .
  • PCFSetLCD(char cmd): Este procedimiento sirve para ejecutar las siguientes instrucciones del controlador de pantalla:  
           - DON/DOFF: Enciende y Apaga la Pantalla            
           - CURSOR_ON/CURSOR_OFF: Oculta o muestra el cursor
           - BLINK_ON/BLINK_OFF: Activa o desactiva el destello del puntero
           - CLEAR: Limpia la pantalla
           - HOME: Retorno el cursor a la posición inicial
  • PCFWritecharLCD(char data): Este procedimiento mostrara un dato o carácter en la posición actual del cursor.
  • PCFWriteLCD(char *str): Muestra en la pantalla una cadena o arreglo de caracteres almacenados en la memoria de datos, el mensaje iniciara desde la posición del cursor. Puede utilizar la variante PCFPutsLCD para cadenas almacenadas en la memoria de programa.
  • PCFGotoLCD(char addr): Posiciona el cursor en la dirección GCADDR de la pantalla LCD, puede usar la variante PCFGotolnLCD para indicar la posición inicial de cada linea o fila de la pantalla LCD. 
        Las direcciones iniciales de la pantalla son las siguientes:
        - 0x00: Linea 0 Posición inicial de la primera fila
        - 0x40: Linea 1 Posición inicial de la segunda fila
        - 0x14: Linea 2 Posición inicial de la tercera fila
        - 0x54: Linea 3 Posición inicial de la cuarta fila
Por ejemplo si deseo escribir el mensaje hola al inicio de la segunda linea (0x40 linea 1) puedo codificar lo siguiente:
PCFGotolnLCD(1); //Seg
    PCFWriteLCD("Ḧola");
 
Ahora si deseo escribir el mensaje al centro de la linea, debo    considerar que la palabra "Hola" posee cuatro letras y la pantalla permite visualizar hasta 16 posiciones, entonces podría codificar los siguiente:
    PCFGotoLCD(0x46); //0x40 + 6 posiciones
    PCFWriteLCD("Hola");
  • PCFSetLED(char mode): Permite encender o apagar la luz de fondo de la pantalla, si el parámetro es 1= Activa y si es 0=desactiva.


Paso 4. Prueba y Simulación.


Ahora escribiremos un pequeño programa para poner en practica y demostrar el uso de una pantalla LCD con este modulo utilizando los procedimientos previamente descritos.

El siguiente código describe un programa que muestra un mensaje inicial en la primera linea y el valor de un contador de segundos en la segunda linea.
  #pragma config FOSC = INTRC_NOCLKOUT, WDTE = OFF, LVP = OFF
  #include <xc.h>
  #include <stdio.h>
  #define _XTAL_FREQ 4000000
  #include "peripheral.h"
  #include "i2c.h"
  #define PCFADDR 0b01001110 //Dirección PC8574 27h
  #include "pcf8574.h"
  char seg = 0, msg[16];
  void main()
  {
    OSCSetup();
    ANSEL = 0;
    ANSELH = 0;
    I2CSetup(I2CSTD); //Inicializa el MSSP
    PCFSetupLCD(LINES2); //Inicia el LCD
    PCFSetLED(1); //Enciende la luz de fondo
    PCFGotoLCD(0x02); //Coloca el cursor en la posición 2 de la fila 0
    PCFPutsLCD("** U.E.B **"); //Muestra el mensaje
    PCFSetLCD(DON & CURSOR_ON & BLINK_OFF);  //Muestra el cursor
    while(true)
    {
        __delay_ms(1000);
        seg = seg + 1; //Incrementa el contador de segundos
        if(seg > 59) seg = 0; //Reinicia el contador de segundos
        PCFGotoLCD(0x40); //Coloca el cursor en la posición 0 de la fila 2
        sprintf(msg, "SEG:%02u", seg); //Cadena personalizada
        PCFWriteLCD(msg); //Muestra la cadena con valores del segundo
    }
}

3.Ensayo del programa con un PIC16F886


Paso 5. Conclusiones y Recomendaciones.

Luego de hacer la demostración del programa podemos concluir que podemos implementar fácilmente este modulo I2C para ahorrarnos entre 4 y 6 lineas o pines del PIC. Si bien la librería solo contiene procedimientos básicos para mostrar mensajes, con un mayor conocimiento de las instrucciones del controlador LCD es posible realizar modificaciones que permitan efectuar operaciones menos frecuentes como ser desplazamientos y generación de caracteres personalizados.
 
Este es el enlace para descargar el ejemplo P8574P16F (MPLABX v5.45 compilador XC8 v1.45)

Esperando que este pequeño aporte te pueda servir de algo, agradezco una vez mas tu visita al blog.

Pablo Zárate Arancibia
Ingeniero Electrónico
pablinzte@gmail.com