ホーム | 電子工作 | 腕ロボット | 二足歩行ロボット | 無線コントローラ | 工作機械 | 動画 | 自作ソフト | Web作成 | テキスト | その他 |
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で終わります。 ![]() |
![]() 温度、湿度、気圧センサーであるBME280の動かし方です。非常に難物でした。次の手順に従います。 1.初期設定をする。 2.補正用のデータをレジスタから入手する。 3.湿度、湿度、気圧の生のデータをレジスタから入手する。 4.3の生のデータを2の補正用データによって実際のデータに直す。 これを詳しく見ていきましょう。 ここに挙げている内容はスイッチサイエンスさんのサイトに載っているものをコピペしたものが多いです。 非常に詳しく載っていますのでより詳しい内容はそのサイトをご覧ください。 1.初期設定をする。 ●configレジスタ(0xF5)は次の項目を設定するためのレジスタです。 t_sb[2:0] スタンバイ時間 filter[2:0] IIRフィルタの有効/無効 spi3w_en[0] 3 wire 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の測定モード設定
●ctrl_humレジスタ(0xF2) osrs_h[2:0] 湿度測定の有効(オーバーサンプリング値)/無告
2.補正用のデータをレジスタから入手する。 温度・湿度・気圧の補正をするための値がアドレス0x88〜0xA1 0xE1〜0xF0に格納されています。
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
センサーを起こすには、センサーのスレーブアドレスである0xB8をセンサーに送ります。 注意しなければならないのは、センサーを呼び覚ます時、スレーブアドレスに対してセンサーはACKを返すことができません。 しかしホストは必ずACKのための9番目のクロック(SCL)を発送しなければなりません。 ホストがSTARTを発行しスレーブアドレスを送信した後に、一定期間(最小800μs〜最大3ms)の待ち時間を入れてからSTOPを発行します。 ホストがハードウェアI2Cの場合は自動的に待機するので、プログラムで待つ必要はありません。 2.STEP2 読みだし命令を送信するには次の手順で読み出し命令を送信します。
温度と湿度のデータが入っているレジスタはアドレス0番からの4個です。 3.STEP3 STEP2の後、1.5msの待ち時間を入れます。
上の表のようにそれぞれ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 |