PIC microcontroller用センサー汎用ライブラリ

戻る一つ前のメニューに戻る

PICマイクロコントローラでセンサーを利用するための汎用ライブラリ

目次

ソースコード

  • 開発言語 Microchip XC 8
  • ターゲットMPU Enhanced Mid-Range 12F1822, 12F1827等

Subversion ブラウザでソースコードを参照する


温湿度センサー DHT11

dht11-price.jpg

1個100円程度のデジタル温湿度センサー。整数値しか取り出せず誤差も±2℃・±5%RHと大きいが、低価格の魅力には使わざるを得ない場合も…。上位機種のDHT22が350円位なので、予算に余裕があるならそちらで。

サンプル回路図と信号タイミング

DHT11は送受信を一つの信号線で兼用するタイプのデジタル センサー モジュール。ホスト(PIC)側からLo信号を18ミリ秒送信すると、DHT11が起動してスタート・ビット(80us Lo + 80 us Hi)とデータ5バイト分を一気に送信してくる。(下図のデータファイルをダウンロード

pic-dht11.png

オシロスコープで波形観察したデータを次に示す。ホスト(PIC側)送信が終了し、読み取りモードとなった時の電圧立ち上がりカーブを右側に示した。プルアップ抵抗によりI/O線が充電される時間を考慮して当Webページに掲載したライブラリには20マイクロ秒待つように設定している。(10マイクロ秒では少し足らなかつた)

dht11.c の処理開始信号部抜粋
    // DHT11に処理開始信号を送る (Loを18ms以上続ける)
    // I/O = Lo
    TRIS_DHT11 = 0; // 「DHT11接続ポート」を出力モードに
    PORT_DHT11 = 0; // 「DHT11接続ポート」 = Lo
    // 25 msec Loを保持(18 msec以上)
    __delay_ms(25);
    // I/O = Hi (入力モードにすることで、自動的にプルアップされる)
    TRIS_DHT11 = 1; // 「DHT11接続ポート」を入力モードに
    // プルアップ抵抗で「DHT11接続ポート」が充電される時間待つ(実測では5〜10usec以上)
    __delay_us(20);

pic-dht11-curve.jpg

ブレッドボード上に制作した回路。DHT11センサーは、網目の面が表で左からVcc, I/O, NC, GNDピンとなっている。

pic-dht11-board.jpg

ソースコード主要部

読込のタイミングに余裕が無いため、不必要なループ処理を削除している。ソースコード中にはタイミングが遅れて読み込みエラーとなるforループ処理の例がコメントアウトされて残存させている。

dht11.c の処理主要部抜粋
#define PORT_DHT11      PORTAbits.RA0
#define TRIS_DHT11      TRISAbits.TRISA0
#define WPUA_DHT11      WPUAbits.WPUA0
 
unsigned char dht11_read_value(unsigned char *value) {
    // DHT11に処理開始信号を送る (Loを18ms以上続ける)
    // I/O = Lo
    TRIS_DHT11 = 0; // 「DHT11接続ポート」を出力モードに
    PORT_DHT11 = 0; // 「DHT11接続ポート」 = Lo
    // 25 msec Loを保持(18 msec以上)
    __delay_ms(25);
    // I/O = Hi (入力モードにすることで、自動的にプルアップされる)
    TRIS_DHT11 = 1; // 「DHT11接続ポート」を入力モードに
    // プルアップ抵抗で「DHT11接続ポート」が充電される時間待つ(実測では5〜10usec以上)
    __delay_us(20);
 
    // DHT11からの開始確認信号を受信する(Lo 80usec, Hi 80usec)
    // 「DHT11接続ポート」 = Lo になるまで待つ
    unsigned char k = 150;    // 300 usec以上待っても終了しない場合のカウンタ
    while(!PORT_DHT11) {
        __delay_us(2);
        if(--k < 1) return(0);  // 異常終了
    }
    // 「DHT11接続ポート」 = Hi になるまで待つ
    k = 150;    // 300 usec以上待っても終了しない場合のカウンタ
    while(PORT_DHT11) {
        __delay_us(2);
        if(--k < 1) return(0);  // 異常終了
    }
 
    // データの読み出し(5バイト)
    *value = dht11_read_byte();         // 湿度 整数部
    *(value+1) = dht11_read_byte();     // 湿度 小数部(DHT11ではゼロ)
    *(value+2) = dht11_read_byte();     // 温度 整数部
    *(value+3) = dht11_read_byte();     // 温度 小数部(DHT11ではゼロ)
    *(value+4) = dht11_read_byte();     // チェックサム
 
// ループで読み出しを行うと、タイミングがずれて読み取れない
//    for (short int i = 0; i < 4; i++) {
//        *(value + i) = dht11_read_byte();
//    }
 
    // チェックサムの確認
    if(*value + *(value+1) + *(value+2) + *(value+3) != *(value+4)) return(0);
 
    // チェックサム結果OK
    return(1);
}
 
unsigned char dht11_read_byte(void) {
      static unsigned short num, i;
      num = 0;
      for (i=0; i<8; i++){
           while(!PORT_DHT11);
           __delay_us(40);  // データが0の場合26-28usec, 1の場合70usecの中間あたりの秒数
           if(PORT_DHT11) num |= 1<<(7-i);
           while(PORT_DHT11);
       }
      return num;
}

 

交流電流センサー CT

ct-6ph.jpg

AC電流測定用の変流器を用いて、PICでAC電流を測る。この測定変流器は抵抗付きのものと、抵抗を自分で用意して取り付けなければならないものがある。日本の電子部品屋で売っているものは抵抗なしの変流器のみが多く、中国の商社から直接輸入する時は抵抗付きのものが多い。

今回はURD社のCTL-6-P-Hという小型変流器を用いた。1Aから10A(100Vで100Wから1000W程度)の測定に用いたいため、特性曲線より100Ωの抵抗を選定した。

ct-6ph-graph.jpg

理想的な正弦波以外の電流測定の場合

理想的な正弦波以外の電流測定の場合のプログラムはこちらのページに記述している。

サンプル回路図

ct-ammeter.png

交流電流センサーは、測定対象の交流波形が「そのまま縮小(減圧)」して出てくるだけなので、特性曲線で1Aと書いてあれば -1A 〜 +1A の範囲の電圧が出てくることになる。

PICのA/D変換器に負電圧を加えるわけにはいかないので、センサーの(仮想的にGNDとする)片方の極を適当な電圧までプルアップして、I/O端子に負電圧が出てこないようにする。今回は、可変抵抗(4kΩ)を用いてVcc/2=2.5V程度にプルアップしている。

実際にクランプメータとにらめっこして、だいたい次の程度の返還率になっていた。
交流センサーの全振幅 1V (±0.5V) → 測定対象の電流 3A
交流センサーの全振幅 2V (±1.0V) → 測定対象の電流 6A

実測波形

ct-ammeter-board.jpg

実測中の回路

ct-ammeter-wave.jpg

約2.5VプルアップしたセンサーI/O出力波形

ソースコード主要部

浮動小数点処理を行わせるとファームウエアのサイズが一気に大きくなるため、表示部分は16進数を簡易的に固定小数点値に変換している。

main.c の処理主要部抜粋
int main(int argc, char** argv) {
    // 基本機能の設定
    OSCCON = 0b01101010;        // 内部オシレーター 4MHz
    TRISA = 0b00101111;         // IOポートRA0(AN0),RA1(SCL),RA2(SDA),RA5(RX)を入力モード(RA3は入力専用)、RA4(TX)を出力モード
    APFCONbits.RXDTSEL = 1;     // シリアルポート RXをRA5ピンに割付
    APFCONbits.TXCKSEL = 1;     // シリアルポート TXをRA4ピンに割付
    ANSELA = 0b00000001;        // A/D変換をAN0を有効、AN1,AN2,AN4を無効
    PORTA = 0;
 
    ADCON0 = 0;                 // AN0選択, A/D機能停止
    ADCON1 = 0b10010000;        // 変換結果右詰, クロックFOSC/8, 比較対象VDD
 
    i2c_enable();
    OPTION_REGbits.nWPUEN = 0;  // I2C プルアップ抵抗 有効
    WPUA |= 0b00000110;          // pull-up (RA1=SCL, RA2=SDA pull-up enable)
 
    i2c_lcd_init();
    i2c_lcd_set_cursor_pos(0);
    printf("AC Ammeter ...");
 
    // 電流値の連続読み出し
    for(;;){
        unsigned int value_max = 0, value_min = 0xffff;      // 検出電流の最大・最小値
        for(unsigned int i = 0; i < 2800; i++){
            ADCON0 = 0b00000001;            // AN0チャンネル選択, A/D機能ON
            __delay_us(10);                 // A/D変換器チャージ時間待つ
            ADCON0 = 0b00000011;            // AN0チャンネル選択, A/D開始, A/D機能ON
            while(ADCON0bits.GO_nDONE){}    // A/D変換完了を待つ
            unsigned int value = ADRESH << 8 | ADRESL;
            ADCON0 = 0;                     // AN0選択, A/D機能停止
 
            if(value > value_max) value_max = value;
            if(value_min > value) value_min = value;
            __delay_us(50);
        }
        // PICのA/D変換は10bit(0〜1024)で5V(Vcc=5.0V)を表現する。
        // A/D変換結果を204.8で割ることで電圧値に変換できる
        i2c_lcd_set_cursor_pos(0);
        printf("max%01d.%01dV min%01d.%01dV", value_max/205, (value_max%205)/21,
                                                value_min/205, (value_min%205)/21);
        // CTの性能が電圧差1V→3Aとする, 5V(1024)→15AなのでA/D変換結果を68.3で割ると電流値
        i2c_lcd_set_cursor_pos(0x40);
        printf("%01d%01d.%01dA (%01d.%01dV)", (value_max - value_min)/683, ((value_max - value_min)%683)/68, ((value_max - value_min)%68)/7,
                (value_max - value_min)/205, ((value_max - value_min)%205)/21);
    }
}

ノイズによる影響

オシロスコープによる観察で、波形上に±0.1V程度のランダム・ノイズが乗っているのが分かる。電源としている「USB電源アダプター」から出ているものなので、乾電池で動くようにすれば少しはノイズが減る。さらに、センサーから基盤までの配線が長いと、空中の電波ノイズを拾ってしまうので、ケーブルも短く…

粉塵センサー GP2Y1010AU0F

pic-dustsensor-ebay.jpg

eBayで1個500円程度の粉塵センサー。対象とする粉塵はSPMなのかPM2.5なのか… 製造メーカーのマニュアルには「粉塵」としか書かれていない。家庭用集じん機などに使うパーツなのだろう。

サンプル回路図と信号タイミング

センサー内のLEDをを点灯し、粉塵で散乱した光の強さを増幅して返す「アナログ」センサー。LEDの店頭タイミングや、散乱光の測定タイミングもユーザが全て制御してやる必要がある。マニュアルに示されたタイミングと、得られる出力電圧と粉じん濃度の関係は次のようなもの。

pic-dustsensor-curve.jpg

このグラフの直線部分を一次回帰曲線として数式化した [粉塵濃度] = ([電圧] - 0.78)/0.0063 をプログラム中で利用する。(一時回帰分析をしたときのLibreOffice Calcファイルをダウンロード

また、マニュアルに示されたサンプル回路を参考に、PIC 12F1822用に少しだけアレンジしたものが次の回路図。(下図のデータファイルをダウンロード

pic-dustsensor-fig.png

pic-dustsensor-photo.jpg

オシロスコープで波形観察したデータを次に示す。マニュアルに示されるとおり、10ミリ秒ごとに0.32ミリ秒だけLEDを点灯し、LED点灯開始から0.28ミリ秒後にセンサーからの出力をA/D変換するプログラムを作成した。そのときのオシロスコープでの観察結果は次の通り。

pic-dustsensor-timing.jpg

また、センサーから返される波形は、少しだけマニュアルと違うがこんな感じだ。

pic-dustsensor-timing3.jpg

ソースコード主要部

1回のみ、決め打ちでA/D変換して粉塵濃度をキャラクタ液晶に表示するプログラム。

void single_measure(unsigned int *value_max, unsigned int *value_min) {
        // 30回測定して、最大値を測定結果として画面表示する
        for(unsigned short int i = 0; i < 30; i++){
            // GP2Y1010AU0FのLEDをONにする320usの間に、1回 A/D変換して電圧値を得る処理
 
            ADCON0 = 0b00000001;            // AN0チャンネル選択, A/D機能ON
            PORTAbits.RA5 = 1;              // 測定LED ON
            __delay_us(280);                // A/D変換器チャージ時間10usを含み280us待つ
            ADCON0 = 0b00000011;            // AN0チャンネル選択, A/D開始, A/D機能ON
            while(ADCON0bits.GO_nDONE){}    // A/D変換完了を待つ
            unsigned int value = ADRESH << 8 | ADRESL;
            ADCON0 = 0;                     // AN0選択, A/D機能停止
            PORTAbits.RA5 = 0;              // 測定LED OFF
 
            if(value > *value_max) *value_max = value;
            if(*value_min > value) *value_min = value;
 
            // GP2Y1010AU0Fの最低測定間隔は10msのため、残り9.7ms待つ
            __delay_us(9700);
 
        }
 
}
 
int main(int argc, char** argv) {
    // 基本機能の設定
    OSCCON = 0b01101010;        // 内部オシレーター 4MHz
    TRISA = 0b00001111;         // IOポートRA0(AN0),RA1(SCL),RA2(SDA)を入力モード(RA3は入力専用)、RA4(未使用),RA5(SensorLED)を出力モード
    ANSELA = 0b00000001;        // A/D変換をAN0を有効、AN1,AN2,AN4を無効
    PORTA = 0;
    ADCON0 = 0;                 // AN0選択, A/D機能停止
    ADCON1 = 0b10010000;        // 変換結果右詰, クロックFOSC/8, 比較対象VDD
 
    i2c_enable();
    OPTION_REGbits.nWPUEN = 0;  // I2C プルアップ抵抗 有効
    WPUA |= 0b00000110;          // pull-up (RA1=SCL, RA2=SDA pull-up enable)
 
    i2c_lcd_init();
    i2c_lcd_set_cursor_pos(0);
    printf("wait...");
 
    PORTAbits.RA5 = 0;
    // 1秒待つ(GP2Y1010AU0F仕様書による指定)
    __delay_ms(1000);
    i2c_lcd_clear();
 
    while(1){
        unsigned int value_max = 0, value_min = 0xffff;      // 検出電圧の最大・最小値
        float v_max = 0, v_min = 0;     // 換算した実電圧
        float v_dust = 0;
 
        single_measure(&value_max, &value_min);
 
        v_max = (float)value_max * 5.0 / 1024.0;
        v_min = (float)value_min * 5.0 / 1024.0;
        v_dust = (v_max - 0.78) / 0.0063;
        i2c_lcd_set_cursor_pos(0);
        printf("Dust %03dug/m3", (int)v_dust);
        i2c_lcd_set_cursor_pos(0x40);
        printf("(max%03d min%03d)", (int)(v_max*100), (int)(v_min*100));
 
        __delay_ms(1000);
    }
    return (EXIT_SUCCESS);
}

上のソースコードで、1回だけA/D変換するところを、マイコンの処理能力が許す限り複数回A/D変換して最大値を得るように改変してみたのが次のプログラム。オシロスコープでタイミングを観察しながら、A/D変換できる最大回数は「たったの4回」。

void multi_measure(unsigned int *value_max, unsigned int *value_min) {
        // 30回測定して、最大値を測定結果として画面表示する
        for(unsigned short int i = 0; i < 30; i++){
            // GP2Y1010AU0FのLEDをONにする320usの間に、1回 A/D変換して電圧値を得る処理
 
            PORTAbits.RA5 = 1;              // 測定LED ON
            for(unsigned short int j = 0; j < 4; j++){
                ADCON0 = 0b00000001;            // AN0チャンネル選択, A/D機能ON
                __delay_us(7);                  // A/D変換器チャージ時間4.97us以上待つ
                ADCON0 = 0b00000011;            // AN0チャンネル選択, A/D開始, A/D機能ON
                while(ADCON0bits.GO_nDONE){}    // A/D変換完了を待つ
                unsigned int value = ADRESH << 8 | ADRESL;
                ADCON0 = 0;                     // AN0選択, A/D機能停止
                __delay_us(1);                  // 時間調整
 
                if(value > *value_max) *value_max = value;
                if(*value_min > value) *value_min = value;
            }
            PORTAbits.RA5 = 0;              // 測定LED OFF
 
            // GP2Y1010AU0Fの最低測定間隔は10msのため、残り9.7ms待つ
            __delay_us(9700);
        }
}

複数回A/D変換するため、すこしだけLED点灯時間が伸びた。オシロスコープでのタイミングの観察結果。

pic-dustsensor-timing2.jpg

マイコンの動作周波数を4MHzから引き上げれば、A/D変換回数をもっと増やすことができるが、消費電力量もそれだけ掛かることになる。

戻る一つ前のメニューに戻る