PIC + ESP-WROOM-02による温度計測

投稿日 2016/09/30

トランジスタ技術2016年9月号にWi-Fi内蔵マイコンESP8266搭載のESP-WROOM-02がとり上げられました。一個約500円と安価で、無線LAN搭載。しかも32bit CPUでプログラミングも可能と、言うこと無しのモジュールです。これで自作派にとってもIoTの世界がより身近になった感があります。

 

今回はトランジスタ技術2016年9月号の記事を参考に、温度計測データをWi-Fiで飛ばし、同じネットワークのサーバ(Raspberry Pi 3B)でグラフ表示してみました。

 

と言うと、トランジスタ技術の記事そのままになってしまいますが、すこし工夫してESP-WROOM-02 1個で多点温度計を作ってみました。(今回試したのは2点)

WROOM_02.jpg

PIC 16F1873 + MCP9700 2個で温度を計測

ESP-WROOM-02を使用してWi-Fiでサーバに送信

サーバ(Raspbery Pi 3B)でグラフ表示

PICとESP-WROOM-02はI2Cで接続

ESP-WROOM-02がマスター、PICはスレーブ

PICの青LEDはI2C通信時に点灯

ESP-WROOM-02側の赤LEDはUDP送信時点灯

 

 

ESP-WROOM-02に搭載のCPUには、残念ならがADコンバータが1チャネルしかないので、複数の温度センサーをつなぐことはできません。しかし安いと言っても、近接の多点の温度を計測するのにWROOMを一台ずつ使うのももったいない話。

 

I2Cの温度センサーなら芋づる式に複数つなぐことができますが、大きさやワイヤ数を考えるとちょっと使いにくいし、I2Cバスを空中に引きまわすのも気が引けます。今回は水槽の温度管理をしたいので、温度センサーにトランジスタと同じ形状のTO-92型のセンサMCP9700(写真)を防水処理して使いました。MCP9700は電圧出力型のセンサーです。

MCP9700.jpg

MIcrochip社の温度センサー MCP9700

TO-92型で小型 精度±2℃ 電源2.3 - 5.5V

0℃ 500mV 10mV/℃

 

 

MCP9700は小型でリードは電源、GND、センサ出力の3本でよく、容量負荷にもつよいのでリードを長く伸ばせます。精度は高くないのですが、0℃の出力が500mVのオフセットがあり、感度10mV/℃でリニアな特性は、複雑な補正などを必要とせず、簡単な計算で温度に換算できる便利で、たいへん使いやすいセンサーです。トランジスタ技術2016年9月号の記事でも採用されています。

 

さて、足らないADコンバータをどうするかですが、今回はPIC 16F1873を使用しI2Cのスレーブにし、ESP-WROOM-02をマスターにしてつなぎました。

PIC16F1783.jpg

PIC16F1783とESP-WROOM-02をI2Cで接続

ESP-WROOM-02がマスタ、PIC 16F1783がスレーブ

2つの温度センサの出力をAD変換し、ESP-WROOM-02に送る

ESP-WROOM-02側はデータを温度に変換して移動平均をとりUDPで送信

 

PIC 16F1873は28ピンのDIPモジュールですから、少々図体が大きいのが玉にきずですが、以下の観点で選びました。

 

12bit ADコンバータ内蔵(11チャネル)

基準電圧源(FVR)内蔵 1.024V, 2.048V, 4.096V

オペアンプ内蔵(2個)

I2C内蔵(1個)

 

今回オペアンプは使いませんでしたが、出力の小さいセンサーなどをつなぐときに活用できます。

 

温度センサーMCP9700の規格上での温度と出力電圧との関係は、

 

0℃ 500mV

10℃ 600mV

20℃ 700mV

30℃ 800mV

40℃ 900mV

50℃ 1V

です。

 

0.1℃あたりは1mVですから、たとえば、25.5℃は500 + (25.5 x 10) = 755mV(0.755V)です。

 

ADコンバータの基準電圧を2.048Vにして、12ビットでAD変換すると、分解能は1ビット当たり0.0005V(0.5mV)ですので、小数一桁目までの精度で読み取れることになります。

 

 今回はMCP9700を2個使用し、ADコンバータのAN2とAN7につなぎました。

 

基準電圧源(FVR)を2.048Vに設定しADコンバータの基準電圧(Vref+)としました。

 

計測したデータは12ビットなので、上位バイトと下位バイトをそれぞれ2チャネル分ESP-WROOM-02にI2Cを通して送ります。

 

ESP-WROOM-02は計測したデータを受け取ると、温度に変換して12回分配列にため込み、移動平均をとってからUDPで転送します。

 

2個のMSP9700は、どうしても計測データに違いが出るので、温度に変換した後、差を補正して合わせました。

 

さらに、もう1個のESP-WROOM-02を用意し高精度(±0.2℃)温湿度センサーHDC1000で同じ部屋の温度を計測(トランジスタ技術2016年9月号のexample09_humプログラムを使用)し、それをリファレンスとして補正しました。(湿度の計測は不要なので削除して使いました。)

二日分の室温.jpg

2日分の室温

ギザギザはエアコンを入り切りしたため

Raspbery Pi 3Bでudp_logger_sarv.shを実行し、gnueplatで表示したグラフ

(詳細はトランジスタ技術2016年9月号参照)

temp._1のグラフ(右下)が一部切れているのは電源断によるもの。

二つの温度センサ(補正あり)は、この温度範囲ではよく合っている。

高精度のHDC1000で計測した温度(左上)ともよく合っている。

 

 

まだ水槽の温度は計測していませんが、バラック配線のまま室温を計測する限りでは、まずまずの結果となっています。途中MCP9700での温度計測が切れていますが、電源断によるものです。2個のMCP9700の温度は、ほとんど重なっています。(赤がセンサーA、緑がセンサーB)。またHDC1000とよくマッチングしているので、問題なく使えそうです。

 

これで2つの水槽(熱帯魚のグッピー)の水温を監視した結果は後日レポートします。

 

補足:

 

今回はESP-WROOM-02を省電力運転するためのsleepは使用していません。sleepを使用すると復帰したときDHCPでIPアドレスを再取得する時に失敗し処理が進まなくなることがあったからです。このため移動平均計算をESP-WROOM-02側でやっています。移動平均は過去の計測データを記憶していなければならないためsleepは使えません。sleepがうまくいくようであれば、移動平均の計算はPIC側でやっとほうがよいと思います。

 

今回は行いませんででしたが、ADコンバータのチャネルが余っているので、センサーを増やすことができます。例えば水槽以外に室温も監視するなど。

 

センサーの電源をPICのGPIOポートからとって、計測時のみONし、省電力化することもできます。今回は電源からとっています。

 

計測は10秒に一回としましたが、スパンを広げて、更に計測しないときはスリープさせればより省電力となります。

 

実行環境の設定と実行方法

 

1. apache2をインストールする

2. gnuplotをインストールする

3. WROOM02, PICに電源投入

4. /var/www/html/index.htmlをグラフ表示用に変更しておく

5. /home/pi/esp/tools/udp_logger_serv.shを実行する

6. apache2を起動する

7. PCやスマホ側からIPアドレスでRAspberryPiにアクセスする

 

 

PIC側のプログラム

 

#include <xc.h>

 

// CONFIG1
#pragma config FOSC = INTOSC
#pragma config WDTE = OFF
#pragma config PWRTE = OFF

#pragma config MCLRE = ON
#pragma config CP = OFF
#pragma config CPD = OFF
#pragma config BOREN = ON
#pragma config CLKOUTEN = OFF
#pragma config IESO = ON
#pragma config FCMEN = ON

// CONFIG2
#pragma config WRT = OFF
#pragma config VCAPEN = OFF
#pragma config PLLEN = OFF
#pragma config STVREN = ON
#pragma config BORV = LO
#pragma config LPBOR = OFF
#pragma config LVP = ON

 

#define _XTAL_FREQ      4000000

#define LED_On LATCbits.LATC2 = 1
#define LED_Off LATCbits.LATC2 = 0

#define I2C2BAUD 9           //9 = ((4MHz / 100KHz) / 4) - 1
#define I2C2_SLAVE_ADDR 0b10100000 //0xA0 -> 0x50(Shifted)

#define TEMP_SENSOR_A 2         //AN2
#define TEMP_SENSOR_B 3         //AN3

 

enum I2C_Stage {
    I2C_STAGE_A_HIGH_BYTE,
    I2C_STAGE_A_LOW_BYTE,
    I2C_STAGE_B_HIGH_BYTE,
    I2C_STAGE_B_LOW_BYTE,
};

 

unsigned char AD_A_LOW;
unsigned char AD_A_HIGH;
unsigned char AD_B_LOW;
unsigned char AD_B_HIGH;
unsigned char I2C_adr;

int i2c_stage;

void interrupt isr(void) {
    if (PIR1bits.SSP1IF == 1) {
        LED_On;
        PIR1bits.SSP1IF = 0;
        //Address
        if (SSPSTATbits.BF == 1 && SSP1STATbits.D_nA == 0) {
            I2C_adr = SSP1BUF;
            SSPBUF = AD_A_HIGH;
            SSPCON1bits.CKP = 1;
            i2c_stage = I2C_STAGE_A_LOW_BYTE;
            //Data(4 bytes))
        } else if (SSPSTATbits.BF == 0 && SSPSTATbits.D_nA == 1 && SSPCON2bits.ACKSTAT == 0) {
            switch (i2c_stage) {
                case I2C_STAGE_A_LOW_BYTE:
                    SSPBUF = AD_A_LOW;
                    SSPCON1bits.CKP = 1;
                    i2c_stage = I2C_STAGE_B_HIGH_BYTE;
                    break;
                case I2C_STAGE_B_HIGH_BYTE:
                    SSPBUF = AD_B_HIGH;
                    SSPCON1bits.CKP = 1;
                    i2c_stage = I2C_STAGE_B_LOW_BYTE;
                    break;
                case I2C_STAGE_B_LOW_BYTE:
                    SSPBUF = AD_B_LOW;
                    SSPCON1bits.CKP = 1;
                    i2c_stage = I2C_STAGE_A_HIGH_BYTE;
                    break;
            }
        }
        LED_Off;
    }
}

 

void init_port(void) {
    OSCCONbits.SCS = 2; //Internal OSC
    OSCCONbits.IRCF = 0x0D; //4MHz
    OSCCONbits.SPLLEN = 0; //4xPLL disable
    TRISA = 0b00001100; // 1:IN 0:OUT
    TRISB = 0b00000000; // 1:IN 0:OUT
    TRISC = 0b00011000; // 1:IN 0:OUT
    TRISE = 0b00000000; // 1:IN 0:OUT
    ANSELA = 0b00001100; // AN2
    ANSELB = 0b00000000; // AN7
    PORTA = 0b00000000;
    PORTB = 0b00000000;
    PORTC = 0b00000000;
    PORTE = 0b00000000;
}

 

void init_i2c(void) {
    SSPCON1bits.SSPM = 6; // I2C Slave mode, 7-bit address
    SPBRG = I2C2BAUD; // 100KHz I2C
    SSP1ADD = I2C2_SLAVE_ADDR; // Slave Address

    PIE1bits.SSP1IE = 1; // MSSP1 割り込み許可
    PIR1bits.SSP1IF = 0; // MSSP1 割り込みフラグクリア

    SSPCON1bits.SSPEN = 1; // Enable the I2C2 module
}

 

void init_adc(void) {
    FVRCONbits.ADFVR = 2; // 2.048V
    FVRCONbits.FVREN = 1; // FVR Enable
    while (FVRCONbits.FVRRDY == 0);
    ADCON1bits.ADFM = 1; // A/D Result Format Right justified
    ADCON1bits.ADCS = 1; //Fosc/8
    ADCON1bits.ADPREF = 3; //Vref = FVR
}

 

void ADC_read(void) {
    INTCONbits.GIE = 0; //グローバル割り込み禁止

    ADCON0 = TEMP_SENSOR_A << 2; //AD Ch A
    ADCON0bits.ADON = 1; //AD On
    ADCON0bits.GO_nDONE = 1; //initiate conversion
    while (ADCON0bits.GO_nDONE) continue;
    AD_A_HIGH = ADRESH & 0x0F;
    AD_A_LOW = ADRESL;
    ADCON0bits.ADON = 0; //AD Off

 

    ADCON0 = TEMP_SENSOR_B << 2; //AD Ch B
    ADCON0bits.ADON = 1; //AD On
    ADCON0bits.GO_nDONE = 1; //initiate conversion
    while (ADCON0bits.GO_nDONE) continue;
    AD_B_HIGH = ADRESH & 0x0F;
    AD_B_LOW = ADRESL;

    ADCON0bits.ADON = 0; //AD Off

    INTCONbits.GIE = 1;
 }

 

void main(void) {
    init_port();
    init_i2c(); //I2C1 Slave 7bit mode    
    init_adc();

 

    INTCONbits.PEIE = 1;

    INTCONbits.GIE = 1;

 

    while (1) {
        ADC_read();
        __delay_ms(1000); //1sec
     };

}

 

 

ESP-WROOM-02側のプログラム

 

#include <ESP8266WiFi.h>
#include <Wire.h>

extern "C" {
    #include "user_interface.h"
}
#include <WiFiUdp.h>
#define PIN_LED 13
#define SSID "*****SSID*****"             // 無線LANアクセスポイントのSSID
#define PASS "*****PWD*****"             // パスワード
#define SENDTO "192.168.1.255"

#define PORT 1024

#define SLEEP_P 10*1000000

#define DEVICE "temp._1,"

unsigned int PIC_Temp_A;
unsigned int PIC_Temp_B;
float cTemp_A;
float cTemp_B;

float Temp_A_ma_table[12];
float Temp_B_ma_table[12];
float Temp_A_ma_val;
float Temp_B_ma_val;
int ma_cnt;
  
void setup(){
    Serial.begin(9600);
    Wire.begin();
       
    int waiting=0; 

    pinMode(PIN_LED, OUTPUT);

    WiFi.mode(WIFI_STA);
    WiFi.begin(SSID,PASS);

    while(WiFi.status() != WL_CONNECTED){

        delay(100);

        waiting++;

        //digitalWrite(PIN_LED,waiting%2);

        if(waiting%10==0)Serial.print('.');

        if(waiting > 300) sleep();
    }
    Serial.println(WiFi.localIP());    
   

    ma_cnt = 0;
    
}

    

void loop(){
    char tmp_string[6];
    char lcd_string[8];
    char serial_string[132];
    int i;
    
    WiFiUDP udp;

    PICSetup();
    
    digitalWrite(PIN_LED, HIGH); 

    udp.beginPacket(SENDTO, PORT);

    udp.print(DEVICE);

    cTemp_A = ((float) PIC_Temp_A * 2.048 / 4096.0 - 0.5) / 0.01;

    cTemp_A += 0.5;

    dtostrf(cTemp_A, 4, 2, tmp_string);    
    sprintf(serial_string,"%s%s", "PIC A: ", tmp_string);
    Serial.println(serial_string);

    cTemp_B = ((float) PIC_Temp_B * 2.048 / 4096.0 - 0.5) / 0.01; 

    cTemp_B += 0.35;
    dtostrf(cTemp_B, 4, 2, tmp_string);    
    sprintf(serial_string,"%s%s", "PIC B: ", tmp_string);
    Serial.println(serial_string);              // シリアル出力表示

    Temp_A_ma_table[ma_cnt] = cTemp_A;
    Temp_A_ma_val = 0;
    for(i = 0; i < 12; i++){
        Temp_A_ma_val = Temp_A_ma_val + Temp_A_ma_table[i];
    }
    Temp_A_ma_val = Temp_A_ma_val / 12.0;
    
    udp.println(Temp_A_ma_val,2);

    udp.print(",");

    Temp_B_ma_table[ma_cnt] = cTemp_B;
    Temp_B_ma_val = 0;    
    for(i = 0; i < 12; i++){
        Temp_B_ma_val += Temp_B_ma_table[i];
    }
    Temp_B_ma_val = Temp_B_ma_val / 12.0;

    ma_cnt++;
    if(ma_cnt >= 12) ma_cnt = 0;
    
    udp.println(Temp_B_ma_val,2);

    udp.endPacket();

    digitalWrite(PIN_LED, LOW );    
    delay(10 * 1000);
}

 

void sleep(){
    delay(200);

    ESP.deepSleep(SLEEP_P,WAKE_RF_DEFAULT);
    while(1){

        delay(100);

    }
}

 

Have a good IoT life!

 

 

  

(JF1VRR)