ホーム

電子工作

腕ロボット

二足歩行ロボット

無線コントローラ

工作機械

電子工作動画

自作ソフト(C言語)

Webページ作成

Javascriptゲーム

FLASHとは!

自学自習のテキスト

その他

PIC18F14K50で温湿気圧センサBME280(2017年2月〜3月)
 温湿度センサAM2320と同じようなものですが、温湿度に加えて大気圧も測定できるセンサBME280に挑戦しました。 やはりIC2インタフェイス仕様です。
モノは秋月で購入しました。左の写真のようにピンヘッダ付属で売られています。¥1,080でした。

 前のAM2320は5V動作なので同じ5V動作であるPIC18F14K50ボードとはすんなりつなげましたが、このBME280 は3V動作です。どうしようか? と思ってネットを調べると「I2Cバス用双方向電圧レベル変換モジュール(PCA9306)」なる ものが秋月で売られていました。

 秋月のコピーは
「I2Cバス用双方向電圧レベル変換専用ICであるPCA9306を使用したモジュールです。 パッド部分が大きくハンダ付けし易い設計。I2Cレベル変換に必要な部品がDIP8ピンのICサイズに!」

 左下の写真です。これもピンヘッダ付きです。先のBME280と同様にピンヘッダとはんだ付けが必要です。

レベル変換を行う際,VREF2 側に高電圧側を接続する必要があります
以下は「I2c通信の使い方」というページからのコピペです。

 I2C通信の場合の接続構成は左図の構成を基本としています。

 図のように1台のマスタと1台または複数のスレーブとの間を、SCLとSDAという2本の線でパーティーライン状に接続します。
 マスタが常に権限を持っており、マスタが送信するクロック信号SCLを元にして、データ信号が SDAライン上で転送されます。ワイアードORで接続するため数kΩのプルアップ抵抗を必要とします。

 通信方式でSPIと大きく異なるのは、個々のスレーブがアドレスを持っていて、マスタからアドレス指定 して特定のスレーブを選択し、1バイト転送ごとに受信側からACK信号の返送をして、互いに確認を 取りながらデータ転送を行っていることです。

 この方式により、いずれもI2cであるAM2320、BME280をつなぐことが出来ます。
 回路図です。

 PIC18F14K50は5V駆動です。AM2320にはちょうどいいのですが、BME280には電圧が大きすぎます。

 三端子レギュレータを使って9V電源から5V電圧と3.3V電圧を作っておいて、3.3VのほうはBME280の方に使います。

 それでBME280だけはI2cバス用双方向電圧レベル変換のPCA9306を介してPICとつなぎます。

 
 
 ブレッドボードに組みました。

 下の方に黄色い丸で囲んでいます。
 左がBME280、右がAM2320です。

 測定結果が液晶モニタに表示されてます。

 1行目がAM2320、2行目がBME280の測定結果です。

 左から気温、大気圧、湿度の順です。

 温度の方はほぼ同じです。
 湿度の方はBME280の方が少し高めに表示されます。
●I2cインタフェイスの書き込み
BME280と情報をやり取りするインタフェイスはI2cですが、その書き込みについて説明します。
大まかな流れは下の図のようです。
RWの値はWRITEモードなので0を入れます。
StartConditionをまず送ります。
Slave AddressのXはSDOピンをどうするか(接続するのがGNDかVDDか)で決まりますが、 僕はGNDにつなぎましたので0になります。
これによってSlave Address+RWは0b11101100になります。
このあと、書き込むレジスタアドレスと書き込むデータを送ります。
最後にStopConditonで終わります。
略字の意味は次です。
★S: Start   ★ P: Stop ★ ACKS: Acknowledge by slave
★ ACKM: Acknowledge by master ★ NACKM: Not acknowledge by master
●I2cインタフェイスの読み込み
読み込みです。
StartConditionをまず送ります。
レジスタを読むためには、まずWriteモードで、読むレジスタアドレスを送ります。
この時のSlave Address+RWは0b11101100です。
StopCondiionかRepeatedStartConditionを送ります。
このあと、Slave AddressをReadモード、つまり0b11101101を送ります。
連続で読み込んでいくとき、NOACKMを送るまでレジスタアドレスは自動的に1ずつ増えます。
最後にStopConditonで終わります。
●やってみて分かったのですが、ACKは読み込みでは絶対に必要ですが、 書き込みではACKを入れると正常に動作しませんでした。
 
 BME280のMemoryMapです。

 温度、湿度、気圧センサーであるBME280の動かし方です。非常に難物でした。次の手順に従います。
1.初期設定をする。
2.補正用のデータをレジスタから入手する。
3.湿度、湿度、気圧の生のデータをレジスタから入手する。
4.3の生のデータを2の補正用データによって実際のデータに直す。
これを詳しく見ていきましょう。

ここに挙げている内容はスイッチサイエンスさんのサイトに載っているものをコピペしたものが多いです。
非常に詳しく載っていますのでより詳しい内容はそのサイトをご覧ください。

1.初期設定をする。
 ●configレジスタ(0xF5)は次の項目を設定するためのレジスタです。
  t_sb[2:0] スタンバイ時間
  filter[2:0] IIRフィルタの有効/無効
  spi3w_en[0] 3 wire SPIの有効/無効
まず、スタンバイ時間です。

スタンバイ時間とはセンサが計測をしないで待機している時間のことです。

スタンバイ時間は左の中から選択します。

1000msを選択しましたので与える値は101になります。
温度と気圧は計測結果を20bitで取得しますが、下位の4bit分(xlsb)はIIRフィルタを有効にしない限り無効で、全て0になります。

しかし、フィルタを有効にすると正しい値を取得するために複数回のサンプリングが必要になります。

フィルタの値が大きいほど必要なサンプリング回数が増えます。

今回Filter offを選択しましたので与える値は000になります。
BME280は3線式と4線式のSPIに対応しています。
spi3w_en[0]を0にすると4線式、1にすると3線式です。ここでは0にします。
よってconfigレジスタの値は 0b10100000 0xA0になります。

●ctrl_measレジスタ(0xF4)は次の項目を設定するためのレジスタです。
  osrs_t[2:0] 温度測定の有効(オーバーサンプリング値)/無効
  osrs_p[2:0] 気圧測定の有効(オーバーサンプリング値)/無効
  mode[1:0] BME280の測定モード設定
温度測定の有効/無効は次のように設定します。

無効にすると結果は0x80000に固定されます。

オーバーサンプリングをすることでノイズによる測定結果への影響を軽減することができますが、測定時間が増加します。

今回、oversampling x 1を選択しましたので与える値は001になります。
気圧測定の有効/無効は次のように設定します。

無効にすると結果は0x80000に固定されます。

オーバーサンプリングをすることでノイズによる測定結果への影響を軽減することができますが、測定時間が増加します。

今回oversampling x 1を選択しましたので与える値は001です。
BME280のモード設定は、 Sleep、Forced、Normalの三つです。

Sleepは計測をしないモード、電源を入れてすぐはSleepモードです。

Forcedモードは1度だけ測定を行うモードです。測定を行うと自動的にSleepモードに戻ります。

Normalモードは繰り返し測定を行うモードです。
測定を繰り返す間隔は温度・湿度・気圧の測定時間とスタンバイ時間を合わせた時間です。

今回はNormalモードを使用しますので与える値は11です。
 以上のことにより、ctrl_measレジスタの値は 0b00100111 0x27になります。

●ctrl_humレジスタ(0xF2)
  osrs_h[2:0] 湿度測定の有効(オーバーサンプリング値)/無告
  湿度測定の有効/無効は次のように設定します。

無効にすると結果は0x80000に固定されます。

オーバーサンプリングをすることでノイズによる測定結果への影響を軽減することができますが、測定時間が増加します。

今回は例としてoversampling x 1を選択しました。

よってctrl_humレジスタの値は 0b00000001 0x01になります。
これらの値をレジスタに書き込んで初期設定は終了です。

2.補正用のデータをレジスタから入手する。
 温度・湿度・気圧の補正をするための値がアドレス0x88〜0xA1 0xE1〜0xF0に格納されています。

 補正用のデータを格納するグローバル変数を定義します。

 真ん中の列がその変数名。

 左列が補正用のデータが格納されているレジスタ名。

 右列がその変数のデータ型です。

 スイッチサイエンスさんのサイトに載っていたプログラムですが、この変数に補正用のデータを保存する関数であるreadTrim()を示します。

 このプログラムはArduino用なのですが、これをPIC用に移植して使うことにします。
void readTrim()
{
    uint8_t data[32],i=0;                      // Fix 2014/04/06
    Wire.beginTransmission(BME280_ADDRESS);
    Wire.write(0x88);
    Wire.endTransmission();
    Wire.requestFrom(BME280_ADDRESS,24);       // Fix 2014/04/06
    while(Wire.available()){
        data[i] = Wire.read();
        i++;
    }
    
    Wire.beginTransmission(BME280_ADDRESS);    // Add 2014/04/06
    Wire.write(0xA1);                          // Add 2014/04/06
    Wire.endTransmission();                    // Add 2014/04/06
    Wire.requestFrom(BME280_ADDRESS,1);        // Add 2014/04/06
    data[i] = Wire.read();                     // Add 2014/04/06
    i++;                                       // Add 2014/04/06
    
    Wire.beginTransmission(BME280_ADDRESS);
    Wire.write(0xE1);
    Wire.endTransmission();
    Wire.requestFrom(BME280_ADDRESS,7);        // Fix 2014/04/06
    while(Wire.available()){
        data[i] = Wire.read();
        i++;    
    }
    dig_T1 = (data[1] << 8) | data[0];
    dig_T2 = (data[3] << 8) | data[2];
    dig_T3 = (data[5] << 8) | data[4];
    dig_P1 = (data[7] << 8) | data[6];
    dig_P2 = (data[9] << 8) | data[8];
    dig_P3 = (data[11]<< 8) | data[10];
    dig_P4 = (data[13]<< 8) | data[12];
    dig_P5 = (data[15]<< 8) | data[14];
    dig_P6 = (data[17]<< 8) | data[16];
    dig_P7 = (data[19]<< 8) | data[18];
    dig_P8 = (data[21]<< 8) | data[20];
    dig_P9 = (data[23]<< 8) | data[22];
    dig_H1 = data[24];
    dig_H2 = (data[26]<< 8) | data[25];
    dig_H3 = data[27];
    dig_H4 = (data[28]<< 4) | (0x0F & data[29]);
    dig_H5 = (data[30] << 4) | ((data[29] >> 4) & 0x0F); // Fix 2014/04/06
    dig_H6 = data[31];                                   // Fix 2014/04/06
}
3.湿度、湿度、気圧の生のデータをレジスタから入手する。
先に挙げたメモリーマップによると、0XF7〜0XFEのレジスタに格納されていますが、
気圧の情報はレジスタのアドレス 0xF7 0xF8 0xF9に格納、
温度の情報はレジスタのアドレス 0xFA 0xFB 0xFCに格納、
湿度の情報はレジスタのアドレス 0xFD 0xFE に格納されています。
湿度だけ2byte、それ以外は3byteです。
これもスイッチサイエンスさんのサイトからのプログラムを載せます。
やはり、PIC用に移植します。
void readData()
{
    int i = 0;
    uint32_t data[8];
    Wire.beginTransmission(BME280_ADDRESS);
    Wire.write(0xF7);
    Wire.endTransmission();
    Wire.requestFrom(BME280_ADDRESS,8);
    while(Wire.available()){
        data[i] = Wire.read();
        i++;
    }
    pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
    temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
    hum_raw  = (data[6] << 8) | data[7];
}
4.3の生のデータを2の補正用データによって実際のデータに直す。
これもスイッチサイエンスさんのサイトのプログラムを載せます。
このプログラムはコピペでこのまま使います。
// Returns temperature in DegC, resolution is 0.01 DegC. 
//Output value of “5123” equals 51.23 DegC.
// t_fine carries fine temperature as global value
signed long int calibration_T(signed long int adc_T)
{
    
    signed long int var1, var2, T;
    var1 = ((((adc_T >> 3) - ((signed long int)dig_T1<<1))) * ((signed long int)dig_T2)) >> 11;
    var2 = (((((adc_T >> 4) - ((signed long int)dig_T1)) * ((adc_T>>4) -
 ((signed long int)dig_T1))) >> 12) * ((signed long int)dig_T3)) >> 14;
    t_fine = var1 + var2;
    T = (t_fine * 5 + 128) >> 8;
    return T; 
}
// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format 
//(24 integer bits and 8 fractional bits).
// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa
unsigned long int calibration_P(signed long int adc_P)
{
    signed long int var1, var2;
    unsigned long int P;
    var1 = (((signed long int)t_fine)>>1) - (signed long int)64000;
    var2 = (((var1>>2) * (var1>>2)) >> 11) * ((signed long int)dig_P6);
    var2 = var2 + ((var1*((signed long int)dig_P5))<<1);
    var2 = (var2>>2)+(((signed long int)dig_P4)<<16);
    var1 = (((dig_P3 * (((var1>>2)*(var1>>2)) >> 13)) >>3) +
 ((((signed long int)dig_P2) * var1)>>1))>>18;
    var1 = ((((32768+var1))*((signed long int)dig_P1))>>15);
    if (var1 == 0)
    {
        return 0;
    }    
    P = (((unsigned long int)(((signed long int)1048576)-adc_P)-(var2>>12)))*3125;
    if(P<0x80000000)
    {
       P = (P << 1) / ((unsigned long int) var1);   
    }
    else
    {
        P = (P / (unsigned long int)var1) * 2;    
    }
    var1 = (((signed long int)dig_P9) * ((signed long int)(((P>>3) * (P>>3))>>13)))>>12;
    var2 = (((signed long int)(P>>2)) * ((signed long int)dig_P8))>>13;
    P = (unsigned long int)((signed long int)P + ((var1 + var2 + dig_P7) >> 4));
    return P;
}
// Returns humidity in %RH as unsigned 32 bit integer in Q22.10 format 
//(22 integer and 10 fractional bits).
// Output value of “47445” represents 47445/1024 = 46.333 %RH
unsigned long int calibration_H(signed long int adc_H)
{
    signed long int v_x1;
    
    v_x1 = (t_fine - ((signed long int)76800));
    v_x1 = (((((adc_H << 14) -(((signed long int)dig_H4) << 20) - (((signed long int)dig_H5) * v_x1)) + 
              ((signed long int)16384)) >> 15) * (((((((v_x1 * ((signed long int)dig_H6)) >> 10) * 
              (((v_x1 * ((signed long int)dig_H3)) >> 11) + ((signed long int) 32768))) >> 10) +
 (( signed long int)2097152)) * 
              ((signed long int) dig_H2) + 8192) >> 14));
   v_x1 = (v_x1 - (((((v_x1 >> 15) * (v_x1 >> 15)) >> 7) * ((signed long int)dig_H1)) >> 4));
   v_x1 = (v_x1 < 0 ? 0 : v_x1);
   v_x1 = (v_x1 > 419430400 ? 419430400 : v_x1);
   return (unsigned long int)(v_x1 >> 12);   
}
PIC18F14K50で温湿度センサAM2320(2017年1月)
 接続形態にI2C(IスクエアCと読む)というものがあります。
マイコンといろいろな機器とやり取りする一つの規格です。
一般には、6線ほどの信号のやり取りが必要です。これではPICの少ないピン数では賄いきれません。
このI2Cという規格はわずか2線で済みます。それでこのI2Cを研究しようか、と思ったわけです。

ところが、このI2Cという規格は非常に難物でなかなか理解できにくいものです。僕もモニタ1602をやる前に I2C規格のモニタを手に入れて何とかしようとがたがたやっていたのですが、なかなかうまくいかず やむなく、簡単そうに思ったこの温湿度センサAM2320をやったのです。

左上がそのAM2320の外観、左下がそのサイズです。単位はmmです。

1 pin=VDD, 2pin=SDA, 3pin=GND, 4pin=SCL
見ての通り4つしかピンはありません。そのうちの2つ(1と3)はVDD(電源)とGNDなので、実質の信号ピン (マイコンとつなぐ)は2つでSDAとSCLと呼ばれています。
この2つのピンをPIC側のSDA、SCLにつなぎます。
PIC18F14K50では、SDAがRB4で、SCLがRB6になります。


I2C通信とは何か! 説明するのは非常に難しいです。

 最初にスタートコンディションを発行して、書き込み、または読み込みをして、最後にストップコンディション を発行して閉めます。

 使うレジスタは沢山あるPICそれぞれで異なります。その意味で使うPICのマニュアルを 読む必要があります。ただネットを調べると先人がいろいろなことを調べて載せて頂いていますのでそれを 探すのも一つの方法でもあります。

詳しいことはでご自分で調べてくださいね。

 
上がAM2320をブレッドボードに組み込んだ全景です。
ギリギリすべてがブレッドボードに収まっています。


左の小さいものがAM2320です。


AM2320のサイズは横幅12.1mm、高さ15.0mmと非常に小さいです。




下がAM2320だけをアップにしたものです。
 温湿度センサAM2320から帰ってきた値です。

 返る値は8個です。上と中はその値をそれぞれ16進数、10進数で表示させたものです

 それぞれ異なる時刻に測定された値なので3つ目以降の値は異なっています。

 順に、 機能コード(Ox03)、返送バイト数(0x04),測定された湿度の上位バイト、測定された湿度の下位バイト、 測定された温度の上位バイト、測定された温度下位バイト、CRCの下位バイト、CRCの上位バイト 、です。

 最も下の表示が算出した温度(temperature)と湿度(humidity)です。
  値 = (256×上位バイト+下位バイト)÷10 で計算できます。
  購入元の秋月電子のサイトからマニュアルをダウンロードして、AM2320のつかい方を調べていきました。
ただ、そのマニュアルは英文の25ページあるものなのでどこから読んだらよいか、と読むだけで大変でした。

方々読んでいくにつれて段々どの辺をよんだらよいかがわかってきました。それで次のようになりました。 AM2320センサーのI2C通信は、標準I2C通信シーケンスに従っています。

 センサーの読み出しと書き込みを行うには、次の3つのステップ従う必要があります。
1.STEP1(センサーを起こす)
2.STEP2(読みだし命令を送信)
3.STEP3(センサが読み取った情報を受信)

1.STEP1

Start Condition
 
スレーブアドレスの送信
Write[0xB8]
NACK 800μs〜3000μsの待ち時間 Stop Condition
センサーが自己発熱による湿度誤差を低減するため休止状態に入っている場合、センサーへの読み書き命令を送る前に、 センサーを休止状態から呼び覚ます必要があります。そうしなければ、センサーは反応しません。
センサーを起こすには、センサーのスレーブアドレスである0xB8をセンサーに送ります。
注意しなければならないのは、センサーを呼び覚ます時、スレーブアドレスに対してセンサーはACKを返すことができません。 しかしホストは必ずACKのための9番目のクロック(SCL)を発送しなければなりません。 ホストがSTARTを発行しスレーブアドレスを送信した後に、一定期間(最小800μs〜最大3ms)の待ち時間を入れてからSTOPを発行します。   ホストがハードウェアI2Cの場合は自動的に待機するので、プログラムで待つ必要はありません。

2.STEP2
読みだし命令を送信するには次の手順で読み出し命令を送信します。

Start
Condition
 
スレーブアドレス+[W]
Write[0xB8+0] 


K 
機能コード
 Write[0x03] 


K 
読みだすStart Adress
Write[0x00] 


K 
読みだすレジスタの数
Write[0x04] 


K 
Stop
CONDITION 
書き込みコマンド[W]は0なので0xB8+0は実質0xB8と変わりません。
温度と湿度のデータが入っているレジスタはアドレス0番からの4個です。

3.STEP3
STEP2の後、1.5msの待ち時間を入れます。

Start
Condition
 
スレーブアドレス+[R]
 Write[0xB8+1]
ACK 
30μ秒以上の待ち時間の後、下の8つを読み込む
 読込
機能コード
 Ox03 


K  
 読込
返送
バイト数


K  
 読込
湿度
上位バイト


K  
 読込
 湿度
下位バイト


K  
 読込
温度
上位バイト


 読込
温度
下位バイト


K  
 読込
 CRC
上位バイト


K  
 読込
 CRC
下位バイト



  Stop
CONDITION 
温度と湿度の算出
上の表のようにそれぞれ2バイトで返送されますので
値 = (256×上位バイト+下位バイト)÷10 で計算できます。
例えば上に示した液晶モニタの値なら
湿度 = (256×0+221)÷10 = 22.1%
温度 = (256×1+ 59)÷10 = 31.5度
すごく低湿度で高温の場所ですね・・・・・・・。
 以下にプログラム全文を載せます。

 I2C通信のプログラムを実際の機器で走らすのははじめてだったので、プログラムを書いて実際に作動するか、 戦々恐々でした。御多分に漏れずうんともすんとも言いませんでした。
 ただ、先に液晶モニタをつけて、その上にAM2320を追加したのがよかったです。

 プログラムの要所要所にその状態(その時点でのルーチン、センサからの返り値)を表示させて「ここまでは動いてる」 という風にして、どこまでが動いているかを確認していきました。パソコンプログラムの「printf debug」と同じ発想です。

 すると、I2Cバスの書き込みの直後にACKとかNACKのルーチンを入れるとそこで止ってしまう、ということが 分かりました。「AM2320のマニュアルにはACK、NACKを入れる」とあったのにですよ・・・・・。
 I2Cバスの書き込みのあとのACK、NACKを外すとそこは通りました。

 ところが、I2Cバスの読み込み後は逆にACK、NACKを入れないとそこで止まりました。ここではそれは要りました。
 つまりACK、NACKは書き込みの時は不要で、読み込みの時は必要だったのです。  こんなことはやってみないと分からないですね。

 これがわかるまでは配線が間違っているのじゃないか? 配線が正しくともきちんと電流がつうじているのか? とかいろんな ことを検証しました。

 マニュアルを読み返してみると「AM2320のSCL、SDAは4.7kΩの抵抗でプルアップする」という図が あったので急遽その抵抗を入れたりしました。このためにAM2320が壊れたのでは・・・・・? と思い、秋月から AM2320を再度購入したりしました。
 結局壊れていなかったのですけれど・・・

 I2C通信についてのプログラムは、「はじめてのPIC」という サイトのプログラムをほぼコピペしました。

 本当に先人の努力には頭が下がります。感謝、感謝・・・・です。

 それから液晶モニタに数値を表示するためには数値を10進数の文字列に変換する必要があります。
 numToString()がこの変換ルーチンです。
 温度、湿度の算出はその値を10で割ったものですが、液晶モニタに表示するには変換した文字列の最後の文字の前に 小数点を挿入するだけでよいです。これをしているのがStrToShosu()です。

 いずれも僕のオリジナルです。よかったら使ってください。
//温度湿度計(AM2320)の測定結果をLCDに表示
//  Notes: 4MHz内部クロック
//         PORTC(3)  :  LCD(DB7)14    PORTC(4) 6  :  LCD_EN 6
//         PORTC(2)  :  LCD(DB6)13    GND         :  LCD_RW 5
//         PORTC(1)  :  LCD(DB5)12    PORTC(5) 5  :  LCD_RS 4
//         PORTC(0)  :  LCD(DB4)11    CONTRAST ADJ:  LCD_Vo 3
//         +5v       :  LCD(Vdd) 2
//         GND       :  LCD(Vss) 1
//    Target: PIC18F14K50
#include 
#include                  
#define _XTAL_FREQ 4000000    
#define LCD     LATC
#define LCD_E   LATCbits.LATC4
#define LCD_RS  LATCbits.LATC5
#define CTRL   0
#define MOJI   1
#define I2cAck()      SSPCON2bits.ACKDT=0;SSPCON2bits.ACKEN=1;while(SSPCON2bits.ACKEN)
#define I2cNAck()     SSPCON2bits.ACKDT=1;SSPCON2bits.ACKEN=1;while(SSPCON2bits.ACKEN)

#pragma config FOSC = IRC        //  内部クロック
#pragma config USBDIV = OFF, CPUDIV = NOCLKDIV
#pragma config IESO  = OFF, FCMEN = OFF, PLLEN  = ON
#pragma config BORV  = 30,  BOREN = OFF, PWRTEN = OFF
#pragma config WDTPS = 32768, WDTEN = OFF
#pragma config MCLRE = OFF, HFOFST = OFF, XINST  = OFF
#pragma config BBSIZ = OFF, LVP    = OFF, STVREN = ON
#pragma config CP1  = OFF, CP0  = OFF, CPD  = OFF, CPB  = OFF
#pragma config WRT1 = OFF, WRT0 = OFF, WRTB = OFF, WRTC = OFF
#pragma config EBTR1 = OFF, EBTR0 = OFF, EBTRB  = OFF

void lcdOut(unsigned char cha,int mode,int bitMode)
{
unsigned char buf=0x00;
if(bitMode == 8){
    LCD_E=0;
     LCD=cha;
    LCD_RS=0;
    LCD_E=1;
    __delay_us(20);
    LCD_E=0;
}
else{
    buf=(cha>>4);
    LCD=buf;
    if( mode == MOJI )  LCD_RS=1;
    LCD_E=1;
    __delay_us(20);
    LCD_E=0;
    if( mode == MOJI)   LCD_RS=0;
    __delay_us(20);//上位4bitと下位4bitの間
    buf=cha;
    LCD=buf;
    if( mode == MOJI )  LCD_RS=1;
    LCD_E=1;
    __delay_us(20);
    LCD_E=0;
    if( mode == MOJI )  LCD_RS=0;
    __delay_us(20);
    }
}
void lcdInit(void)
{
    __delay_ms(20);
    lcdOut(0x03,0,8);
    __delay_ms(4);
    lcdOut(0x03,0,8);
    __delay_ms(4);
    lcdOut(0x03,0,8);//8bit mode
    __delay_ms(4);
    lcdOut(0x02,0,8);//4bit mode
    __delay_ms(4);
    lcdOut(0x08,CTRL,4);//2行表示モード
    __delay_ms(4);
    lcdOut(0x0c,CTRL,4);//表示設定
    __delay_ms(4);
    lcdOut(0x06,CTRL,4);//entry mode
    __delay_ms(4);
    lcdOut(0x01,CTRL,4);//cler
    __delay_ms(20);
}
void lcdPos(int x, int y)
{
    if( y == 1) lcdOut(0x80+x,CTRL,4);
    else lcdOut(0xC0+x,CTRL,4);    
}
 void lcdDsp(unsigned char *str,int x, int y)
{
    unsigned char *ad=0; 
    LCD_RS=0;
    lcdPos(x,y);
    __delay_ms(2);
    ad=str;
    while(*ad)
    {
    lcdOut(*ad,MOJI,4);
    ad++;
    }
}
char *numToString(int su,char *s)
{	
	int i,k,ret,num,num1,num2,len;
	char s1[25];
	for(i=1;i<=20;i++)	s1[i-1] = 0x20;
	s1[20]=0;
	k=1;
	num = su;
	while(1){
		num1 = num % 10 +'0';
		s1[20-k]=num1;
		num1 = num /10;
		num = num1;
		if(num1<1)	break;
		k++;
	}
	strcpy(s,s1+20-k);
	return (s);
}
void I2cStart( void )
{
  SSPCON2bits.SEN = 1;            // initiate bus start condition
   while(SSPCON2bits.SEN);  // Start condition 確認

}void I2cStop( void )
{
  SSPCON2bits.PEN = 1;            // initiate bus stop condition
  while(SSPCON2bits.PEN);  // Stop condition 確認    

}
signed char I2cWrite( unsigned char data ){
    SSPBUF = data;                 // データセット
    PIR1bits.SSPIF = 0;            // 終了フラグクリア
    while(!PIR1bits.SSPIF);        // 送信終了待ち
    return  SSPCON2bits.ACKSTAT;    // ACKなら「0」で復帰
}
unsigned char I2cRead( void )
{
  SSPCON2bits.RCEN = 1;            // データ受信を指示
  while ( !SSPSTATbits.BF );       // 8ビット受信の完了を待つ
  return  SSPBUF;                  // 受信データで復帰 
}
char *StrToShosu(char *namaStr,char *ShosuStr)
{
	char tmpStr[30];
    int len;
	strcpy(tmpStr,namaStr);
   len = strlen(namaStr);
    *(tmpStr+len-1) = 0;
    strcat(tmpStr,".");
    strcat(tmpStr,namaStr+len-1);
	strcpy(ShosuStr,tmpStr);
	return ShosuStr;
}
void main(void){
     int i,temp,humi;
     unsigned char retData[10],s1[20],ss[10],sss[30],ret2;
     signed char ret1;
     
    OSCCON = 0b01010010;         // 内部クロック4Mhz
    LATC = 0;                    // PortCのすべてのビットを「0」
    TRISC = 0b00000000;          // ポートを出力に設定
    TRISB = 0b11111111;          // Bポートを入力に設定
   ANSEL  = 0b00000000;  //すべてデジタル
    ANSELH = 0b00000000;
    // SSP1設定 -----------------------------------------------
    SSPCON1 = 0b00001000;               // I2C Master modeにする
    SSPCON2 = 0x00;                     // PowerOn初期値にする
    SSPSTAT = 0b10000000;               // スルーレート制御はOff @100k
    SSPADD  = 9;                        // クロックの設定    100k@4MHz
    SSPCON1bits.SSPEN = 1;              // SSP 有効にする 
   
   lcdInit();
while(1){
//Wake Sensor
    I2cStart();
    I2cWrite(0xB8);                //slave address
    __delay_us(800);
    I2cStop();
 
    I2cStart();          			//STEP2
    I2cWrite(0xB8);                //slave address+W(0)
    I2cWrite(0x03);                
    I2cWrite(0x00);
    I2cWrite(0x04);                
    I2cStop();
  
     //Return Data
    __delay_us(1500);
    I2cStart();          // STEP3
    I2cWrite(0xB9);   //slave address+R(1)
    __delay_us(30);
    for(i=1;i<=7;i++){
        retData[i-1] = I2cRead();
        I2cAck(); 
    }
   retData[7] = I2cRead();
    I2cNAck();
    I2cStop();
 
    temp = retData[2]*256+retData[3];
    humi = retData[4]*256+retData[5];

    numToString(temp,ss);//「tempera=〇deg」を作り表示
    StrToShosu(ss,s1);
    strcpy(sss,"tempera =");
    strcat(sss,s1);
    strcat(sss,"deg");
    lcdDsp(sss,0,1);
    
    numToString(humi,ss);//[humidity=〇%]を作り表示
    StrToShosu(ss,s1);
    strcpy(sss,"humidity=");
    strcat(sss,s1);
    strcat(sss,"%");
    lcdDsp(sss,0,2);
    
    for(i=1;i<=50;i++) __delay_ms(100);//5秒待つ
            }
 }