第35回 温湿度・気圧センサ(BME280) 〜データ取得プログラム〜

センサ(BME280)のデータシートを元に、温湿度・気圧データの取得プロクグラムを完成させます。

目次

測定データ取得の流れ

温湿度・気圧センサBME280の概要の記事で説明しましたが、もう一度測定データ取得の流れを確認します。なお、以前の記事で同じような図を説明しましたが、SPI通信手順の関係から、一部の処理手順をまとめてあります。

Pic practice 35 data process

この図のように電源投入後、(1)動作設定を行い、(2)補正データを取得します。この処理は電源投入後1回だけ行います。

次に測定の度に、(3)測定指示を行い、(4)測定データを取得して、(5)データ補正をします。

この後、この処理ごとにプログラムを解説します。なお、データ取得のプログラムはほとんどがデータシートの通りに作成することになります。そのため、プログラムの作成よりデータシートを読み解くことが重要、というか結構大変な作業なので、心が折れそうになった時でも前に進む気持ちが一番大切です。

読み込み方法

ところでセンサのデータを読み込む方法ですが、以前説明したようにSPI通信でアドレス(1バイト)を送信した後クロックを8ビット分発生すると、そのアドレスのデータ(1バイト=8ビット)を返信してくれます。

さらにこの後クロックを発生し続けると、その次のアドレスのデータを返信してくれます。

これまでの記事で、指定したアドレスの1バイトを読み込む関数を作成しましたが、2バイト(16ビット)の読み込みも多用しますので、2バイト読み込み関数も作成しておきます。

また、動作設定は特定のアドレスに1バイトのデータを書き込むことになりますので、1バイトデータを送信する関数も作成しておきます。

SPI関数の追加

それでは、2バイト(16ビット)を読み込む関数を作成します。読み込みたいアドレスの1バイトを送信するとそのアドレスの1バイトのデータを読み込むことができ、さらにクロックを発生すると次のアドレスのデータを読み込むことができます。なお、取得した2バイトは、最初の1バイトが上位バイト、次の1バイトが下位1バイトになります。

1バイト読み込み関数を流用して以下のように作成しました。(すみません、表示不具合があります。この関数は最後のプログラムでは正常に表示されています)

//
// SPIデータを指定アドレスから2バイト読み込み
// 
uint16_t spiRead2BytesData(uint8_t address) {

    uint8_t  data_low, data_high;  // 読み込んだ1バイトデータ格納用
    uint16_t data;  // 2バイトデータ用

    // チップセレクトを0にしてセンサモジュールとの通信開始
    SPI_CSB = 0;

    // 読み込みデータアドレス指定
    spiSend8bit(address);

    // 指定アドレスとその次のアドレスのデータを2バイト読み込み
    data_low  = spiReceive8bit();
    data_high = spiReceive8bit();

    //16ビットデータにする
    data = (data_high << 8) | data_low;

    // チップセレクトを1にして通信終了
    SPI_CSB = 1;

    return data;

}

また、1バイト書き込む場合は、アドレスの1バイトとデータの1バイトを連続して送信すればOKです。なお、書き込む際のアドレスは、最上位ビットを0にします。つまり読み込みアドレスに対して0b01111111と&をとればOKです。以下のように作成してみました。

//
// SPIデータを指定アドレスに1バイト書き込み
//
void spiWrite1ByteData(uint8_t address, uint8_t data) {

    // チップセレクトを0にしてセンサモジュールとの通信開始
    SPI_CSB = 0;

    // アドレス指定(書き込みは最上位ビット0)
    spiSend8bit(address & 0b01111111);

    // データ書き込み
    spiSend8bit(data);

    // チップセレクトを1にして通信終了
    SPI_CSB = 1;
  
}

(1)動作設定プログラム

動作設定は、BME280メモリマップの以下のメモリに設定値を書き込むことにより行います。

Pic practice 35 memory map config

動作設定のメモリは「config」「ctrl_meas」「ctrl_hum」の3個で、それぞれのアドレスの設定内容は以下のようになっています。

Pic practice 35 config register

動作設定のメモリは3個(3バイト)ですが、これらはさらに細かく分かれています。それぞれの設定値を詳しく説明すると長くなってしまいますので、必要な内容をなるべく簡潔に説明します。(とはいっても長くなりますが…)

t_sb(スタンドバイ時間)

このセンサは、温湿度・気圧データの測定タイミングとして2つのモードがあります。

ひとつは、一定の時間間隔で連続して測定を行うモードです。例えば500ms毎に測定する、などです。この場合、センサは(勝手に)500ms毎に継続して測定を行います。PICマイコン側は任意のタイミングでデータを取得でき、その時取得できるデータは最新の測定したデータになります。このセンサでは、この時間間隔のことを「スタンドバイ時間」と呼んでいるようです。

もうひとつは、普段はスリープモードになっていて、PICマイコンから測定指示した時に測定する、というモードです。PICマイコンから測定指示をすると、このセンサはスリープモードから動作モードに移行して温湿度・気圧を測定後、すぐにスリープモードに戻ります。なお、スリープモードでも測定したデータの取得はできます。今回はこのモードで測定を行います。

t_sbは連続測定モードの時間間隔設定で、以下の値を取ります。なお、連続測定モードを使用しない場合はこの設定は無効になります。デフォルトの0を指定しておきます。

設定値 設定内容(単位:ms)
000 0.5
001 62.5
010 125
011 250
100 500
101 1000
110 10
111 20

filter(フィルタ)

どのようなセンサもノイズの影響を受けます。そのため同じ環境下、例えば気圧が全く変わらない室内で測定した場合でも、測定値は若干のばらつきがあります。

そこで、測定したデータをそのまま使用するのではなく、今までの測定値と今回の測定値の平均をとってノイズの影響を減らす手法があります。株式投資やFXをされている方であれば「移動平均線」をご存知ではないでしょうか。移動平均線は、過去何日分かの価格の平均値を求めることにより、変動を滑らかにする手法です(=ノイズを減らしている)。

このセンサのfilter設定もそれと同じです。ただ、計算方法は株式投資やFXでいうところの指数移動平均線に近い計算を行なっています。

このfilter設定は、過去データをどのぐらい重要視するか、という設定で、数値が大きいほど過去のデータを重視してノイズの影響をより低減することができます。ただ数値が大きいほど今回の測定値はすぐに反映されなくなりますので、測定値自体の精度は落ちます。

設定値 設定内容
000 OFF
001 2
010 4
011 8
上記以外 16

データシートによると、環境測定の場合はfilter設定はOFFが推奨されています。今回はOFFで使用します。

spi3w_en

今までのSPI通信手順では、通信線は4本で説明してきましたが、3本線を使用する通信手順もあります。その場合はMOSIとMISO信号線を1本で実現することになります。通信手順は4本線の場合とほぼ同じです。ただ、3本線はあまり使用されていませんので説明は省略いたします。

spi3w_enはSPI通信の信号線数を指定します。以下の値を取ります。

設定値 設定内容
0 4線式
1 3線式

4線式で使用しますので、0を設定します。

osrs_t / osrs_p / osrs_h(オーバーサンプリング)

先ほどのfilter設定は、ノイズの影響を低減するために「測定値」の平均値を取るものでした。ノイズを低減する方法として、他の手法があります。

応用編で「ADコンバータ」を説明しました。このセンサも温湿度・気圧センサ自体はアナログ値のため、センサ内部でADコンバータを使用してデジタル値に変換しています。

ADコンバータでアナログ値をデジタル値に変換する時、通常は1回測定してそのデータを使用します。ただ、1回の測定ではノイズの影響を受けている可能性が高いため、より短い時間間隔で測定してノイズの影響を減らす方法があります。この方法は「オーバーサンプリング」と呼ばれています(数学的にはかなり難しい内容のため、概念のみの説明にとどめます)。

orsr_t/osrs_p/osrs_h設定は、どのくらい頻度を多くして測定するか(頻度を何倍にするか)、という設定で、以下の値をとります。「_t」は温度用、「_p」は気圧用、「_h」は湿度用のオーバーサンプリング設定です。

なお、この設定で「測定しない」という選択もできます。測定そのものは電力を消費しますので、電池を長持ちさせたい場合は必要なければ測定をスキップすることができます。

設定値 設定内容
000 測定しない
001 x1
010 x2
011 x4
100 x8
上記以外 x16

データシートによると、環境データ測定の場合で測定頻度が低い場合はオーバーサンプリング設定はx1で十分、とのことで、設定はx1にしてみます。

mode(動作モード)

センサの測定モードを指定します。センサの動作モードは「スリープモード」「連続測定モード」「1回測定モード」の3種類あります。

なお先ほど説明しましたが、電源投入後はスリープモードの状態で、「1回測定モード」に設定した場合、温湿度・気圧データ測定を行い、そのあと自動的にすぐにスリープモードに戻ります。

設定値 設定内容
00 スリープモード
01と10 1回測定後スリープモードに戻る
11 連続測定モード

以上の内容から、メモリの必要なところに設定値を書き込むプログラムを作成します。なお、設定値は以下のようにしました。(データシートの推奨値にしています)

0

設定項目 設定値 設定内容
t_sb 0 測定時間間隔設定は使用しないため0を設定
filter 0 フィルタは使用しない
spi3w_en SPIは4線式
osrs_t 1 温度オーバーサンプリングはx1
osrs_p 1 気圧オーバーサンプリングはx1
osrs_h 1 湿度オーバーサンプリングはx1
mode 0 スリープモード
//
// BME280初期化関数
//
void bme280Initialization(void) {

    // 動作パラメータ設定
    uint8_t t_sb     = 0;  // スタンドバイ時間は使用しない
    uint8_t filter   = 0;  // フィルタOFF
    uint8_t spi3w_en = 0;  // SPIは4線式(=0)
    uint8_t osrs_t   = 1;  // 温度オーバーサンプリング x1
    uint8_t osrs_p   = 1;  // 大気圧オーバーサンプリング x1
    uint8_t osrs_h   = 1;  // 湿度オーバーサンプリング x1
    uint8_t mode     = 0;  // スリープモード

    // 設定値をフォーマットに合わせる
    uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
    uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
    uint8_t ctrl_hum_reg  = osrs_h;

    // BME280動作パラメータ書き込み
    spiWrite1ByteData(0xF2, ctrl_hum_reg);
    spiWrite1ByteData(0xF4, ctrl_meas_reg);
    spiWrite1ByteData(0xF5, config_reg);
    
    // センサ処理待ち
    __delay_ms(1000);

    // 補正値読み込み
    //   センサごとに固定ちのため、初期化時のみ読み込む
    bme280ReadTrimmingParameters();

}

(2)補正データ取得プログラム

ここからはデータシートに従ってプログラムを作成することになります。中身の詳細まではデータシートに説明されていませんので、ちょっと気持ち悪いですが中身がよくわからないままのプログラムとなります。

測定データを取得した後、補正データで補正する必要があります。補正データはセンサ個体ごとに異なりますが、あるセンサの補正データは固定で変わりませんので、電源投入後に1度取得したら、そのまま保存しておきます。そのため、補正データはグローバル変数で宣言しておきます。

補正データはメモリマップの以下のアドレスに保存されています。

Pic practice 35 calibration address

補正データはcalib00〜calib41の合計42バイトあります。この42バイトですが、データシートでは「以下のように変数に読み込んでおいてください」と説明がありますので、この通りにします。(中身がわからないのでこの通りにするしかない)

Pic practice 35 calibration data structure
(BOSCH社「BME280データシート」より引用・加工)

ちょっと表現がわかりづらいですが、例えば最初のアドレスにあるdig_T1はデータタイプはunsigned shourt (符号なし16ビット)で宣言しますが、

0x88/0x89  dig_T1[7:0] / [15:8]

というのは、0x88番地のデータをdig_T1の第7ビット〜第0ビット、つまり下位1バイトに格納し、0x89番地のデータをdig_T1の第15ビット〜第8ビット、つまり上位1バイトに格納しなさい、という意味になります。データシートからこの辺りを読み取るのも心が折れそうになるところです。

ということで、この表に従って以下のようにグローバル変数を宣言します。

// BME280気温補正データ
uint16_t dig_T1;
int16_t  dig_T2;
int16_t  dig_T3;

// BME280湿度補正データ
uint8_t  dig_H1;
int16_t  dig_H2;
uint8_t  dig_H3;
int16_t  dig_H4;
int16_t  dig_H5;
int8_t   dig_H6;

// BME280気圧補正データ
uint16_t dig_P1;
int16_t  dig_P2;
int16_t  dig_P3;
int16_t  dig_P4;
int16_t  dig_P5;
int16_t  dig_P6;
int16_t  dig_P7;
int16_t  dig_P8;
int16_t  dig_P9;

次にこれらのデータを指定されたアドレスから読み取ればよいので以下のようにプログラムを作成します。

//
// 補正データ読み込み
//
void bme280ReadTrimmingParameters(void) {

    // 気温データ用補正データ
    dig_T1 = spiRead2BytesData(0x88);
    dig_T2 = (int16_t)spiRead2BytesData(0x8A);
    dig_T3 = (int16_t)spiRead2BytesData(0x8C);

    // 気圧データ用補正データ用補正データ
    dig_P1 = spiRead2BytesData(0x8E);
    dig_P2 = (int16_t)spiRead2BytesData(0x90);
    dig_P3 = (int16_t)spiRead2BytesData(0x92);
    dig_P4 = (int16_t)spiRead2BytesData(0x94);
    dig_P5 = (int16_t)spiRead2BytesData(0x96);
    dig_P6 = (int16_t)spiRead2BytesData(0x98);
    dig_P7 = (int16_t)spiRead2BytesData(0x9A);
    dig_P8 = (int16_t)spiRead2BytesData(0x9C);
    dig_P9 = (int16_t)spiRead2BytesData(0x9E);

    // 湿度データ用補正データ用補正データ
    dig_H1 = spiRead1ByteData(0xA1);
    dig_H2 = (int16_t)spiRead2BytesData(0xE1);
    dig_H3 = spiRead1ByteData(0xE3);
    dig_H4 = (int16_t)((spiRead1ByteData(0xE4) << 4) | (spiRead1ByteData(0xE5) & 0x0F));
    dig_H5 = (int16_t)((spiRead1ByteData(0xE6) << 4) | (spiRead1ByteData(0xE5) >> 4));
    dig_H6 = (int8_t)spiRead1ByteData(0xE7);

}

(3)測定指示プログラム

測定指示を行うには、(1)動作設定プログラムのmodeを0b01に設定すればOKです。

(1)の関数を流用して以下のようにプログラムを作成しました。

なお、測定指示をしてから測定が完了するまで数ms程度時間がかかるようです。BME280のレジスタにはStatusレジスタがあり、このレジスタを監視していると測定が完了したかどうかわかるようになっています。ただ、while文でこのレジスタを監視するようにプログラムしたのですが、どうしてもうまく動かなかったため、10ms時間待ちするようにプログラムしてあります。

//
// 温湿度・気圧データ測定指示
//
void bme280ForcedMeasurement(void) {

    // 動作パラメータ設定
    uint8_t t_sb     = 0;  // スタンドバイ時間は使用しない
    uint8_t filter   = 0;  // フィルタOFF
    uint8_t spi3w_en = 0;  // SPIは4線式
    uint8_t osrs_t   = 1;  // 温度オーバーサンプリング x1
    uint8_t osrs_p   = 1;  // 大気圧オーバーサンプリング x1
    uint8_t osrs_h   = 1;  // 湿度オーバーサンプリング x1
    uint8_t mode     = 1;  // 測定指示(1回測定したらスリープモードに移行)

    // 設定値をフォーマットに合わせる
    uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
    uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
    uint8_t ctrl_hum_reg  = osrs_h;

    // BME280動作パラメータ書き込み
    spiWrite1ByteData(0xF2, ctrl_hum_reg);
    spiWrite1ByteData(0xF4, ctrl_meas_reg);
    spiWrite1ByteData(0xF5, config_reg);
    
    // 測定待ち
    //   statusレジスタを監視して測定完了を判断すべきだが
    //   while文での動作ができなかったので時間待ちで代用
    __delay_ms(10);
    
    // この段階でBME280の温湿度大気圧レジスタに測定値が格納されている
    // このあと自動的にスリープモードに入る


}

(4)測定データ取得プログラム

1回の測定指示をした後は、以下のメモリに測定したデータが格納されています。

Pic practice 35 measured data

SPI通信でこれらのメモリアドレスのデータを読み取るだけですので、特に難しいところはないと思います。以下のようにプログラムを作成しました。メモリが連続していますので、最初のアドレス(0xF7)を指定した後、一気に8バイトを読み取っている点に注意してください。

なお、press_raw, temp_raw, hum_rawはuint32_t型としてグローバル変数宣言しています。あとで補正関数で補正計算するためです。

// グローバル変数
//
// BME280温湿度・気圧読み取りデータ
uint32_t hum_raw, temp_raw, pres_raw;

// 
// 補正前の生の温湿度・気圧データ読み込み
// 
void bme280ReadMeasuredRawData() {

    // データ読み取り用配列
    uint32_t data[8];

    // スレーブセレクトアクティブ
    SPI_CSB = 0;

    // 読み込み開始アドレス指定
    spiSend8bit(0xF7 | 0b10000000);

    // 8バイト分のデータ読み込み
    for(int8_t i=0; i<8; i++){
      data[i] = spiReceive8bit();
    }

    // スレーブセレクトインアクティブ
    SPI_CSB = 0;

    // 読み込みしたデータから気温、湿度、気圧データを生成
    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];

}

(5)補正プログラム

最後に、取得した補正データを補正するプログラムです。データ補正方法についてはデータシートにCのサンプルコードがありますので、それを参考に作成します。

まず、データシートに記載されている温度の補正関数です。

// 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
BME280_S32_t t_fine;

BME280_S32_t BME280_compensate_T_int32(BME280_S32_t adc_T)
{
    BME280_S32_t var1, var2, T;
    var1 = ((((adc_T>>3) – ((BME280_S32_t)dig_T1<<1))) * ((BME280_S32_t)dig_T2)) >> 11;
    var2 = (((((adc_T>>4) – ((BME280_S32_t)dig_T1)) * ((adc_T>>4) – ((BME280_S32_t)dig_T1))) >> 12) *
((BME280_S32_t)dig_T3)) >> 14; t_fine = var1 + var2;
    T =(t_fine*5+128)>>8;

    return T;
}

(BOSCH社「BME280データシート」より引用)

次に、湿度の補正関数です。

// 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
BME280_U32_t bme280_compensate_H_int32(BME280_S32_t adc_H)
{
    BME280_S32_t v_x1_u32r;

    v_x1_u32r = (t_fine – ((BME280_S32_t)76800));
    v_x1_u32r = (((((adc_H << 14) – (((BME280_S32_t)dig_H4) << 20) – (((BME280_S32_t)dig_H5) * v_x1_u32r)) +
        ((BME280_S32_t)16384)) >> 15) * (((((((v_x1_u32r * ((BME280_S32_t)dig_H6)) >> 10) * (((v_x1_u32r * ((BME280_S32_t)dig_H3)) >> 11) + ((BME280_S32_t)32768))) >> 10) + ((BME280_S32_t)2097152)) *
((BME280_S32_t)dig_H2) + 8192) >> 14));

    v_x1_u32r = (v_x1_u32r – (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((BME280_S32_t)dig_H1)) >> 4)); v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
    v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);

    return (BME280_U32_t)(v_x1_u32r>>12);
}

(BOSCH社「BME280データシート」より引用)

最後は、気圧の補正関数ですが、データシートの本文には64ビットで計算するプログラムが記載されています。PIC16F18857の変数型は32ビットまでですので、データシートの付録に記載されている32ビット版のプログラムを参考にします。

// Returns pressure in Pa as unsigned 32 bit integer. Output value of “96386” equals 96386 Pa = 963.86 hPa
BME280_U32_t BME280_compensate_P_int32(BME280_S32_t adc_P) {
    BME280_S32_t var1, var2;
    BME280_U32_t p;

    var1 = (((BME280_S32_t)t_fine)>>1) – (BME280_S32_t)64000;
    var2 = (((var1>>2) * (var1>>2)) >> 11 ) * ((BME280_S32_t)dig_P6);
    var2 = var2 + ((var1*((BME280_S32_t)dig_P5))<<1);
    var2 = (var2>>2)+(((BME280_S32_t)dig_P4)<<16);
    var1 = (((dig_P3 * (((var1>>2) * (var1>>2)) >> 13 )) >> 3) + ((((BME280_S32_t)dig_P2) * var1)>>1))>>18;
    var1 =((((32768+var1))*((BME280_S32_t)dig_P1))>>15);

    if (var1 == 0)
    {
        return 0; // avoid exception caused by division by zero
    }

    p = (((BME280_U32_t)(((BME280_S32_t)1048576)-adc_P)-(var2>>12)))*3125;
    if (p < 0x80000000)
    {
        p = (p << 1) / ((BME280_U32_t)var1);
    }
    else
    {
       p = (p / (BME280_U32_t)var1) * 2;
    }

    var1 = (((BME280_S32_t)dig_P9) * ((BME280_S32_t)(((p>>3) * (p>>3))>>13)))>>12; var2 = (((BME280_S32_t)(p>>2)) * ((BME280_S32_t)dig_P8))>>13;
    p = (BME280_U32_t)((BME280_S32_t)p + ((var1 + var2 + dig_P7) >> 4));

    return p;
}

(BOSCH社「BME280データシート」より引用)

さて、、、、、、、、困りました。このままMPLABX IDEでビルドするとエラーになりますので、一つ一つ解決していきましょう。

まず、見慣れない変数型があります。「BME280_S32_t」や「BME280_U32_t」などです。これらはデータシートに説明されていて「「BME280_S32_t」は符号付32ビット、「BME280_U32_t」符号なし32ビット変数型とのことです。「S」がsigned、「U」がunsignedの意味で、数字がビット数のようです。これらは、「int32_t」や「uint32_t」などに置き換えます。

また、t_fineという変数がありますが、これはグローバル変数として宣言するようです。温度補正関数の中で計算後、湿度と気圧補正時にも使用するようです。

なお、これらの関数で補正した値は、そのまま実際の温湿度・気圧表示に使用できません。以下の変換をする必要があります。

(実際の温度) = (補正後の温度値) / 100
(実際の湿度) = (補正後の湿度値) / 1024
(実際の気圧) = (補正後の気圧値) / 100

これらの情報をもとに、補正プログラムは以下のように作成しました。関数の中でt_fineという変数が出てきますが、int32_t型のグローバル変数で宣言しています。また、サンプルプログラムでは、補正前のデータを引数にしていますが、以下のプログラムではグローバル変数にしています。

// グローバル変数
//
// BME280温湿度・気圧読み取りデータ
uint32_t hum_raw, temp_raw, pres_raw;
int32_t  t_fine;

//
// 気温データ補正
//   何をしているのかよくわからない
// 
int32_t bme280CompensateTemperature() {
  
    int32_t var1, var2, T;

    var1 = ((((temp_raw >> 3) - ((int32_t)dig_T1<<1))) * ((int32_t)dig_T2)) >> 11;
    var2 = (((((temp_raw >> 4) - ((int32_t)dig_T1)) * ((temp_raw>>4) - ((int32_t)dig_T1))) >> 12) * ((int32_t)dig_T3)) >> 14;
    t_fine = var1 + var2;
    T = (t_fine * 5 + 128) >> 8;

    return T; 

}

//
// 気圧データ補正
//   何をしているのかよくわからない
// 
uint32_t bme280CompensatePressure() {

    int32_t var1, var2;
    uint32_t P;

    var1 = (((int32_t)t_fine)>>1) - (int32_t)64000;
    var2 = (((var1>>2) * (var1>>2)) >> 11) * ((int32_t)dig_P6);
    var2 = var2 + ((var1*((int32_t)dig_P5))<<1);
    var2 = (var2>>2)+(((int32_t)dig_P4)<<16);
    var1 = (((dig_P3 * (((var1>>2)*(var1>>2)) >> 13)) >>3) + ((((int32_t)dig_P2) * var1)>>1))>>18;
    var1 = ((((32768+var1))*((int32_t)dig_P1))>>15);
    if (var1 == 0)
      return 0;

    P = (((uint32_t)(((int32_t)1048576)-pres_raw)-(var2>>12)))*3125;
    if(P<0x80000000)
      P = (P << 1) / ((uint32_t) var1);   
    else
      P = (P / (uint32_t)var1) * 2;

    var1 = (((int32_t)dig_P9) * ((int32_t)(((P>>3) * (P>>3))>>13)))>>12;
    var2 = (((int32_t)(P>>2)) * ((int32_t)dig_P8))>>13;
    P = (uint32_t)((int32_t)P + ((var1 + var2 + dig_P7) >> 4));

    return P;

}

//
// 湿度データ補正
//   何をしているのかよくわからない
// 
uint32_t bme280CompensateHumidity() {

    int32_t v_x1_u32r;

    v_x1_u32r = (t_fine - ((int32_t)76800));
    v_x1_u32r = (((((hum_raw << 14) -(((int32_t)dig_H4) << 20) - (((int32_t)dig_H5) * v_x1_u32r)) + 
             ((int32_t)16384)) >> 15) * (((((((v_x1_u32r * ((int32_t)dig_H6)) >> 10) * 
             (((v_x1_u32r * ((int32_t)dig_H3)) >> 11) + ((int32_t) 32768))) >> 10) + ((int32_t)2097152)) * 
             ((int32_t) dig_H2) + 8192) >> 14));
    v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((int32_t)dig_H1)) >> 4));
    // v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
    if( v_x1_u32r < 0 ){
        v_x1_u32r = 0;
    }
    // v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
    if( v_x1_u32r > 419430400 ) {
        v_x1_u32r = 419430400;
    }

    return (uint32_t)(v_x1_u32r >> 12);

}

プログラム

最後に、温湿度・気圧データを取得するプログラムにまとめます。データ取得、補正後にLEDを点滅させるプログラムを追加しています。

/*
 * BME280温湿度大気圧センサ
 * 制御プログラム
 * 
 * https://tool-lab.com/
 *
 * 更新
 *   2018. 5.16: 新規作成
 * 
 */

//
// PIC16F18857コンィグレーション設定
//
// CONFIG1
#pragma config FEXTOSC = OFF    // External Oscillator mode selection bits (Oscillator not enabled)
#pragma config RSTOSC = HFINT1  // Power-up default value for COSC bits (HFINTOSC (1MHz))
#pragma config CLKOUTEN = OFF   // Clock Out Enable bit (CLKOUT function is disabled; i/o or oscillator function on OSC2)
#pragma config CSWEN = ON       // Clock Switch Enable bit (Writing to NOSC and NDIV is allowed)
#pragma config FCMEN = ON       // Fail-Safe Clock Monitor Enable bit (FSCM timer enabled)
//
// CONFIG2
#pragma config MCLRE = OFF      // Master Clear Enable bit (IO)
#pragma config PWRTE = ON       // Power-up Timer Enable bit (PWRT enabled)
#pragma config LPBOREN = OFF    // Low-Power BOR enable bit (ULPBOR disabled)
#pragma config BOREN = ON       // Brown-out reset enable bits (Brown-out Reset Enabled, SBOREN bit is ignored)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices)
#pragma config ZCD = OFF        // Zero-cross detect disable (Zero-cross detect circuit is disabled at POR.)
#pragma config PPS1WAY = ON     // Peripheral Pin Select one-way control (The PPSLOCK bit can be cleared and set only once in software)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable bit (Stack Overflow or Underflow will cause a reset)
//
// CONFIG3
#pragma config WDTCPS = WDTCPS_31// WDT Period Select bits (Divider ratio 1:65536; software control of WDTPS)
#pragma config WDTE = OFF       // WDT operating mode (WDT Disabled, SWDTEN is ignored)
#pragma config WDTCWS = WDTCWS_7// WDT Window Select bits (window always open (100%); software control; keyed access not required)
#pragma config WDTCCS = SC      // WDT input clock selector (Software Control)
//
// CONFIG4
#pragma config WRT = OFF        // UserNVM self-write protection bits (Write protection off)
#pragma config SCANE = available// Scanner Enable bit (Scanner module is available for use)
#pragma config LVP = ON         // Low Voltage Programming Enable bit (Low Voltage programming enabled. MCLR/Vpp pin function is MCLR.)
//
// CONFIG5
#pragma config CP = OFF         // UserNVM Program memory code protection bit (Program Memory code protection disabled)
#pragma config CPD = OFF        // DataNVM code protection bit (Data EEPROM code protection disabled)

//
// ヘッダファイル
//   int_t型を使用するため、stdint.hをインクルード
//
#include <xc.h>
#include <stdint.h>

//
// 定数
//

// SPIピン設定
#define SPI_SCK    LATCbits.LATC7
#define SPI_MISO   PORTCbits.RC6
#define SPI_MOSI   LATCbits.LATC5
#define SPI_CSB    LATCbits.LATC4

//
// 関数プロトタイプ宣言
//

//   BME280センサー制御
void     bme280Initialization(void);            // BME280の初期化
void     bme280ForcedMeasurement(void);         // 温湿度・気圧データの測定指示
void     bme280ReadTrimmingParameters(void);    // 補正データの読み取り
void     bme280ReadMeasuredRawData(void);       // 補正前の温湿度・気圧データ読み取り
int32_t  bme280CompensateTemperature(void);     // 温度データ補正
uint32_t bme280CompensatePressure(void);        // 気圧データ補正
uint32_t bme280CompensateHumidity(void);        // 湿度データ補正

// SPI通信制御関数
uint16_t spiRead2BytesData(uint8_t address);
uint8_t  spiRead1ByteData(uint8_t address);
void     spiWrite1ByteData(uint8_t address, uint8_t data);
void     spiSend8bit(uint8_t data);
uint8_t  spiReceive8bit();

// クロック周波数
// __delay_ms()関数が時間基準に使用する
#define _XTAL_FREQ 4000000

// グローバル変数
//
// BME280温湿度・気圧読み取りデータ
uint32_t hum_raw, temp_raw, pres_raw;
int32_t  t_fine;

// BME280気温補正データ
uint16_t dig_T1;
int16_t  dig_T2;
int16_t  dig_T3;

// BME280湿度補正データ
uint8_t  dig_H1;
int16_t  dig_H2;
uint8_t  dig_H3;
int16_t  dig_H4;
int16_t  dig_H5;
int8_t   dig_H6;

// BME280気圧補正データ
uint16_t dig_P1;
int16_t  dig_P2;
int16_t  dig_P3;
int16_t  dig_P4;
int16_t  dig_P5;
int16_t  dig_P6;
int16_t  dig_P7;
int16_t  dig_P8;
int16_t  dig_P9;


//
// main関数
//
void main(void) {

    // 動作周波数設定
    OSCCON1bits.NDIV = 0b0000;  // 分周1:1
    OSCFRQbits.HFFRQ = 0b010;   // 4MHz
    
    // ピン属性設定
    ANSELA = 0b00000000;
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00000000;
    TRISB  = 0b00000000;
    TRISC  = 0b01001100;
    
    // SPI信号線初期設定
    SPI_SCK  = 0;  // クロックを0
    SPI_MOSI = 0;  // マスタ→スレーブを0
    SPI_CSB  = 1;  // チップセレクトを1(=無効)

    
    // BME280初期化
    //   コントロールレジスタ、補正係数取得
    bme280Initialization();

    // 実際の温湿度(人が読む場合の数値)
    float actual_temp;
    float actual_hum;
    float actual_press;

    // 補正データ(補正後の数値)
    int32_t  compensated_temp;
    uint32_t compensated_hum;
    uint32_t compensated_press;

    while(1){

        // SPI通信で温湿度センサの値を取得
        bme280ForcedMeasurement();
        bme280ReadMeasuredRawData();

        // 取得したデータをキャリブレーション
        compensated_temp  = bme280CompensateTemperature();
        compensated_hum   = bme280CompensateHumidity();
        compensated_press = bme280CompensatePressure();

        // 実際の温湿度に変換
        actual_temp  = (float)compensated_temp  / 100.0;
        actual_hum   = (float)compensated_hum   / 1024.0;
        actual_press = (float)compensated_press / 100.0;

        LATBbits.LATB0 = 1;
        LATBbits.LATB1 = 1;
        __delay_ms(1000);
        LATBbits.LATB0 = 0;
        LATBbits.LATB1 = 0;
        __delay_ms(1000);
    }

}

//
// BME280初期化関数
//
void bme280Initialization(void) {

    // 動作パラメータ設定
    uint8_t t_sb     = 0;  // スタンドバイ時間は使用しない
    uint8_t filter   = 0;  // フィルタOFF
    uint8_t spi3w_en = 0;  // SPIは4線式(=0)
    uint8_t osrs_t   = 1;  // 温度オーバーサンプリング x1
    uint8_t osrs_p   = 1;  // 大気圧オーバーサンプリング x1
    uint8_t osrs_h   = 1;  // 湿度オーバーサンプリング x1
    uint8_t mode     = 0;  // スリープモード

    // 設定値をフォーマットに合わせる
    uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
    uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
    uint8_t ctrl_hum_reg  = osrs_h;

    // BME280動作パラメータ書き込み
    spiWrite1ByteData(0xF2, ctrl_hum_reg);
    spiWrite1ByteData(0xF4, ctrl_meas_reg);
    spiWrite1ByteData(0xF5, config_reg);
    
    // センサ処理待ち
    __delay_ms(1000);

    // 補正値読み込み
    //   センサごとに固定ちのため、初期化時のみ読み込む
    bme280ReadTrimmingParameters();

}


//
// 温湿度・気圧データ測定指示
//
void bme280ForcedMeasurement(void) {

    // 動作パラメータ設定
    uint8_t t_sb     = 0;  // スタンドバイ時間は使用しない
    uint8_t filter   = 0;  // フィルタOFF
    uint8_t spi3w_en = 0;  // SPIは4線式
    uint8_t osrs_t   = 1;  // 温度オーバーサンプリング x1
    uint8_t osrs_p   = 1;  // 大気圧オーバーサンプリング x1
    uint8_t osrs_h   = 1;  // 湿度オーバーサンプリング x1
    uint8_t mode     = 1;  // 測定指示(1回測定したらスリープモードに移行)

    // 設定値をフォーマットに合わせる
    uint8_t ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode;
    uint8_t config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en;
    uint8_t ctrl_hum_reg  = osrs_h;

    // BME280動作パラメータ書き込み
    spiWrite1ByteData(0xF2, ctrl_hum_reg);
    spiWrite1ByteData(0xF4, ctrl_meas_reg);
    spiWrite1ByteData(0xF5, config_reg);
    
    // 測定待ち
    //   statusレジスタを監視して測定完了を判断すべきだが
    //   while文での動作ができなかったので時間待ちで代用
    __delay_ms(10);
    
    // この段階でBME280の温湿度大気圧レジスタに測定値が格納されている
    // このあと自動的にスリープモードに入る

}


//
// 補正データ読み込み
//
void bme280ReadTrimmingParameters(void) {

    // 気温データ用補正データ
    dig_T1 = spiRead2BytesData(0x88);
    dig_T2 = (int16_t)spiRead2BytesData(0x8A);
    dig_T3 = (int16_t)spiRead2BytesData(0x8C);

    // 気圧データ用補正データ用補正データ
    dig_P1 = spiRead2BytesData(0x8E);
    dig_P2 = (int16_t)spiRead2BytesData(0x90);
    dig_P3 = (int16_t)spiRead2BytesData(0x92);
    dig_P4 = (int16_t)spiRead2BytesData(0x94);
    dig_P5 = (int16_t)spiRead2BytesData(0x96);
    dig_P6 = (int16_t)spiRead2BytesData(0x98);
    dig_P7 = (int16_t)spiRead2BytesData(0x9A);
    dig_P8 = (int16_t)spiRead2BytesData(0x9C);
    dig_P9 = (int16_t)spiRead2BytesData(0x9E);

    // 湿度データ用補正データ用補正データ
    dig_H1 = spiRead1ByteData(0xA1);
    dig_H2 = (int16_t)spiRead2BytesData(0xE1);
    dig_H3 = spiRead1ByteData(0xE3);
    dig_H4 = (int16_t)((spiRead1ByteData(0xE4) << 4) | (spiRead1ByteData(0xE5) & 0x0F));
    dig_H5 = (int16_t)((spiRead1ByteData(0xE6) << 4) | (spiRead1ByteData(0xE5) >> 4));
    dig_H6 = (int8_t)spiRead1ByteData(0xE7);

}


// 
// 補正前の生の温湿度・気圧データ読み込み
// 
void bme280ReadMeasuredRawData() {

    // データ読み取り用配列
    uint32_t data[8];

    // スレーブセレクトアクティブ
    SPI_CSB = 0;

    // 読み込み開始アドレス指定
    spiSend8bit(0xF7 | 0b10000000);

    // 8バイト分のデータ読み込み
    for(int8_t i=0; i<8; i++){
      data[i] = spiReceive8bit();
    }

    // スレーブセレクトインアクティブ
    SPI_CSB = 0;

    // 読み込みしたデータから気温、湿度、気圧データを生成
    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];

}

//
// 気温データ補正
//   何をしているのかよくわからない
// 
int32_t bme280CompensateTemperature() {
  
    int32_t var1, var2, T;

    var1 = ((((temp_raw >> 3) - ((int32_t)dig_T1<<1))) * ((int32_t)dig_T2)) >> 11;
    var2 = (((((temp_raw >> 4) - ((int32_t)dig_T1)) * ((temp_raw>>4) - ((int32_t)dig_T1))) >> 12) * ((int32_t)dig_T3)) >> 14;
    t_fine = var1 + var2;
    T = (t_fine * 5 + 128) >> 8;

    return T; 

}


//
// 気圧データ補正
//   何をしているのかよくわからない
// 
uint32_t bme280CompensatePressure() {

    int32_t var1, var2;
    uint32_t P;

    var1 = (((int32_t)t_fine)>>1) - (int32_t)64000;
    var2 = (((var1>>2) * (var1>>2)) >> 11) * ((int32_t)dig_P6);
    var2 = var2 + ((var1*((int32_t)dig_P5))<<1);
    var2 = (var2>>2)+(((int32_t)dig_P4)<<16);
    var1 = (((dig_P3 * (((var1>>2)*(var1>>2)) >> 13)) >>3) + ((((int32_t)dig_P2) * var1)>>1))>>18;
    var1 = ((((32768+var1))*((int32_t)dig_P1))>>15);
    if (var1 == 0)
      return 0;

    P = (((uint32_t)(((int32_t)1048576)-pres_raw)-(var2>>12)))*3125;
    if(P<0x80000000)
      P = (P << 1) / ((uint32_t) var1);   
    else
      P = (P / (uint32_t)var1) * 2;

    var1 = (((int32_t)dig_P9) * ((int32_t)(((P>>3) * (P>>3))>>13)))>>12;
    var2 = (((int32_t)(P>>2)) * ((int32_t)dig_P8))>>13;
    P = (uint32_t)((int32_t)P + ((var1 + var2 + dig_P7) >> 4));

    return P;

}

//
// 湿度データ補正
//   何をしているのかよくわからない
// 
uint32_t bme280CompensateHumidity() {

    int32_t v_x1_u32r;

    v_x1_u32r = (t_fine - ((int32_t)76800));
    v_x1_u32r = (((((hum_raw << 14) -(((int32_t)dig_H4) << 20) - (((int32_t)dig_H5) * v_x1_u32r)) + 
             ((int32_t)16384)) >> 15) * (((((((v_x1_u32r * ((int32_t)dig_H6)) >> 10) * 
             (((v_x1_u32r * ((int32_t)dig_H3)) >> 11) + ((int32_t) 32768))) >> 10) + ((int32_t)2097152)) * 
             ((int32_t) dig_H2) + 8192) >> 14));
    v_x1_u32r = (v_x1_u32r - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) * ((int32_t)dig_H1)) >> 4));
    // v_x1_u32r = (v_x1_u32r < 0 ? 0 : v_x1_u32r);
    if( v_x1_u32r < 0 ){
        v_x1_u32r = 0;
    }
    // v_x1_u32r = (v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r);
    if( v_x1_u32r > 419430400 ) {
        v_x1_u32r = 419430400;
    }

    return (uint32_t)(v_x1_u32r >> 12);

}


//
// SPIデータを指定アドレスから2バイト読み込み
// 
uint16_t spiRead2BytesData(uint8_t address) {

    uint8_t  data_low, data_high;  // 読み込んだ1バイトデータ格納用
    uint16_t data;  // 2バイトデータ用

    // チップセレクトを0にしてセンサモジュールとの通信開始
    SPI_CSB = 0;

    // 読み込みデータアドレス指定
    spiSend8bit(address);

    // 指定アドレスとその次のアドレスのデータを2バイト読み込み
    data_low  = spiReceive8bit();
    data_high = spiReceive8bit();

    //16ビットデータにする
    data = (data_high << 8) | data_low;

    // チップセレクトを1にして通信終了
    SPI_CSB = 1;

    return data;

}


//
// SPIデータを指定アドレスから1バイト読み込み
// 
uint8_t spiRead1ByteData(uint8_t address) {

    // 受信データ格納変数
    uint8_t data;

    // チップセレクトを0にしてセンサモジュールとの通信開始
    SPI_CSB = 0;

    // SPI通信手順によりアドレスを送信
    spiSend8bit(address);

    // SPI通信手順によりデータを受信
    data = spiReceive8bit();

    // チップセレクトを1にして通信終了
    SPI_CSB = 1;

    // 受信したーデータを返す
    return data;

}


//
// SPIデータを指定アドレスに1バイト書き込み
//
void spiWrite1ByteData(uint8_t address, uint8_t data) {

    // チップセレクトを0にしてセンサモジュールとの通信開始
    SPI_CSB = 0;

    // アドレス指定(書き込みは最上位ビット0)
    spiSend8bit(address & 0b01111111);

    // データ書き込み
    spiSend8bit(data);

    // チップセレクトを1にして通信終了
    SPI_CSB = 1;
  
}

//
// SPIデータ8ビット書き込み
//   SCK/MOSI制御のための関数であるため
//   スレーブセレクト信号はこの関数の前後で制御すること
void spiSend8bit(uint8_t data) {

    // 8ビット分繰り返す
    for (int8_t i=7; i>=0; i--) {

        // (1)クロックを0にする
        SPI_SCK = 0;
        
        // (2)MOSIにデータをセットする
        if( data & (1<<i) ) {
            SPI_MOSI = 1;
        } else {
            SPI_MOSI = 0;
        }
      
        // (3)クロックを1にする
        SPI_SCK = 1;
    }

}

//
// SPIデータ8ビット読み込み
//   SCK/MOSI制御のための関数であるため
//   スレーブセレクト信号はこの関数の前後で制御すること
//
uint8_t spiReceive8bit() {

    // 受信データ格納変数
    uint8_t read_data = 0;

    // 8ビット分繰り返す
    for (int8_t i=7; i>=0; i--) {

        // 受信データ変数を1ビット左シフト
        read_data <<= 1;

        // (1)クロックを0にする
        SPI_SCK = 0;
        
        // (2)クロックを1にする
        SPI_SCK = 1;

        // (3)この時点でセンサからのデータを読めるので、MISOのピン状態を読む
        if(SPI_MISO){
            read_data |= 1;
        }
    }

    // 受信したデータを返す
    return read_data;

}

動作確認

それでは動作確認をしてみます。

センサからデータ取得をして補正データの計算ができるとLEDが点滅しますので、まずはそのままビルド→PICマイコンに書き込んでLEDが点滅するか確認してみてください。

次に、デバッグ機能を利用して実際に温湿度・気圧データが取得できているか確認してみてください。

気温は実際よりも2〜3度高くなっていればOKです。湿度は、冬であれば20%〜50%程度、夏であれば60%以上であればだいたい合っていると思います。

気圧については、おそらく気圧計をお持ちの方は少ないと思いますので、以下の手順で現在の気圧の値を計算します。

まず、気象庁のアメダスのサイトで一番近い地点の気圧を調べます。以下のサイトにアクセスして、近い地点の地域を選択して気圧を調べます。

気象庁アメダス

Pic practice 35 pressure data

このサイトで確認できるデータは、海抜0mの気圧です。気圧は標高が10m高くなると約1hPa低くなりますので、現在測定している場所の標高を以下の手順で調べます。

国土地理院のサイトに標高を調べるページがありますので、アクセスします。

国土地理院 標高データサイト

検索機能がありますので、住所やランドマークなどを入力して検索します。

Pic practice 35 altitude map 1

近辺の地図が表示されたら、地図の拡大縮小を行い、中央の十字を調べたい地点に合わせます。十字地点の標高は以下に表示されます。(以下の画像はJR新宿駅西口前あたりです)

Pic practice 35 altitude map 2

なお、戸建ての2階以上やマンションの場合は、この標高よりも高くなりますので、だいたいの高さを足してみてください。

10mあたり約1hPa低くなりますので、海抜0mの気圧データから、標高分の気圧を引くと測定しているところの気圧がわかります。その値とそれほどずれていなければ正しい値が取得できています。

ところで、、、

プログラムがかなり複雑になってきましたよね。SPI通信部分はそれほど難しくないと思いますが、センサを制御するプログラム部分が難しいところだと思います。ただ、センサ制御の部分はデータシートに従って作成する、という感じですのでプログラミングの難しさより、データシートを読んで理解する方がかなり難しいです。

この後の記事では、温度センサと液晶ディスプレイのプログラムを作成していきますが、同様にデータシートを読んでいくことから始まります。

かなり難しく感じるかもしれませんが、一つ一つ紐解いていけば絶対に理解できますので、すぐにわからなくても時間をかけて理解していきましょう!

更新履歴

日付 内容
2018.5.17 新規投稿
2019.5.14 BME280の強制測定モードの際、測定が完了するまで待つ処理(10ms)を追加
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
初学者
初学者
4 年 前

基本的な質問となりますが回答をお願いしたく
投稿をさせていただきました。
(4)測定データ取得プログラム に記載されている
17行目の

// 読み込み開始アドレス指定
spiSend8bit(0xF7 | 0b10000000);

の部分になりますが、8bit目を1にする必要があるためと
考えておりますが、あっておりますでしょうか?
またそれはなぜなのでしょうか?

管理者
管理者
返信  初学者
4 年 前

ご質問どうもありがとうございます。
結論としては、BME280では読み出しアドレスは最上位ビットが1、書き込みアドレスは最上位ビットが0という仕様のためです(第29回の記事で解説しておりますので、ご参照いただければと思います)

なお、
spiSend8bit(0xF7 | 0b10000000);
の0b10000000で論理和をとるのはあまり意味がありませんが、読み出し、書き込みを明示的にするために書いています。ただ、読み出しの場合はこのように書かず、
spiSend8bit(0xF7);
でも全く問題ありません。

初学者
初学者
返信  管理者
4 年 前

早速のご返答ありがとうございます。
第29回参考にさせていただきました。

尚、例のspiSend8bitにて
「SPI通信でこれらのメモリアドレスのデータを読み取るだけですので、特に難しいところはないと思います。以下のようにプログラムを作成しました。メモリが連続していますので、最初のアドレス(0xF7)を指定した後、一気に8バイトを読み取っている点に注意してください。」
とありますが、8バイトを一気に読み取る点について
以下の質問があります。

・8バイトとなるとアドレスで言うところの0xF7から0xFEまで一度になるのでしょうか?
・もし上記の通りだとすると、PICマイコンではこのような仕様が普通なのでしょうか?PICマイコンのデータシートに記載はありますでしょうか?
・上記8バイトのうち一部部だけ読み込むことはできないのでしょうか?

何卒よろしくお願いいたします。

初学者
初学者
4 年 前

幾度も質問をしておりますが
消されてしまっておりますので
再度質問させていただきます。

(4)測定データ取得プログラム
SPI通信でこれらのメモリアドレスのデータを読み取るだけですので、特に難しいところはないと思います。以下のようにプログラムを作成しました。メモリが連続していますので、最初のアドレス(0xF7)を指定した後、一気に8バイトを読み取っている点に注意してください。

上記の「一気に8バイトを読み取る」とのことですが
このような仕様なのでしょうか?

管理者
管理者
返信  初学者
4 年 前

ご質問どうもありがとうございます。

データを読み取る方法ですが、アドレスを指定→1バイト読み取り、アドレスを指定→1バイト読み取り、という方法で毎回アドレスを指定して読み取る方法があります。ただ、この場合いちいちアドレスを指定するのは面倒です。

そのためBME280ではアドレスを指定して1バイト読み出したあと、読み出しをつづけるると次のアドレスのデータを読み取ることができます。

第29回の「読み出し方法」のところで図解していますので参考にしていただければと思います。

Guest
Guest
4 年 前

基礎編からお世話になり、ここまで読み進めてまいりました。
非常にわかりやすく、勉強になります。

さて、bme280ReadMeasuredRawData関数内で下記の処理を実施しておりますが、何故12ビット左シフトなのでしょうか。
pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);

4byteデータとして生成するので24bitシフトかと思いましたが、ご解説いただけると幸いです。

管理者
管理者
返信  Guest
4 年 前

回答が遅くなり申し訳ございませんでした(コメントの通知がきていませんでした…)

第29回の記事でBME280の詳細仕様を説明しておりますが、気圧データ20ビットとなっています。変数pres_rawは4バイト(32ビット)ですが、気圧データ(20ビット)のデータを入れる変数としてuint16_tでは足りないので、uint32_tを使用しているだけで、実際に使用するのは20ビット部分です。(20ビットのデータを入れる入れ物として32ビットの入れ物を使用しているだけです)

ネモ
ネモ
5 年 前

(再送)
いつも丁寧な解説をありがとうございます。
このデータ取得プログラムのデバッグについて教えて下さい。
MPLABX IDEでのデバッグでメイン関数内の実際の温度に変換された値を見ることができません。
変換後の172行目にブレークポイントを設定し、actual_変数の値を見ようとするのですが、画像のように
「Not Recognized」と表示されます。
幸いcompensated_変数の値は見れるので、電卓で計算してそれらしい値が入っていることは確認できています。
環境:
回路(LEDは省略)とプログラムは連載と同じです。
MPLABX v5.30
XC8 v2.10
PICKit3
Windows10 Pro・Home どちらも64bit
ブレークポイントを変えたり、ステップ実行してみたり、Watchsリストに入れたりしたのですが、なぜかダメです。
電圧はPICKitのプロジェクトのPowerから3.25供給にしています。外部電源でも試してみました。
今ある知識とネットでの検索で原因が分かりませんでした。
何かデバッガ用の設定などが必要でしょうか?comment image

管理者
管理者
返信  ネモ
4 年 前

回答が遅くなり申し訳ございませんでした(コメントの通知がきていませんでした…)

デバッグ時に変数をみても “Not Recongnized” となって変数の中身がみられないのは、コンバイラの最適化処理によるものだと思われます。

「最適化処理」とは、例えば以下のようなプログラムがあった場合、

void main(void) {
char a;

while( true ) {
}

}

このプログラムをデバッグしても、デバッグ時には変数aは “Not Recognized” となります。コンパイラはこのプログラムをコンパイルする時、変数aは宣言したものの使われていないので、PICマイコンに送るコードでは省略してしまいます。そのためデバッグしても変数aは認識されない、ということになります。

actual_…変数は、実際に計算して、グローバル変数に代入して使用していますので、コンパイルされてもいいような気がしますが、何かの最適化がされていてデバッグでは認識できないのかもしれません。

詳しい調査はしていませんが、最適化によるもので特に何か悪い、というわけではありません。

ネモ
ネモ
返信  管理者
4 年 前

丁寧なご回答ありがとうございます。表示される場合とされない場合があるのはそういう理由があるんですね。
特に設定などに問題が無いようですので、表示されている部分で確認したり、無理やり使うようにして見えるようにして使ってみようと思います。

管理者
管理者
返信  ネモ
4 年 前

すみません、実際の動作を未確認ですが、コンパイラの最適化を避ける場合、「volatile」という修飾子を使用すると避けることができます。

volatile float actual_temp;

というように宣言すると、actual_tempが実際にコンパイルされてデバッグ時に確認できるかもしれません。「volatile」はコンパイラが最適化するのを避ける指示をするもので、愚直にコンパイルします。世の中では「生産性向上」なんて言われていますが、volatileはそれと逆行するような感じの指示をします。

自信ない回答ですみません…

ネモ
ネモ
5 年 前

いつも丁寧な解説をありがとうございます。
このプログラムでのデバッグについて質問させてください。
回路(LEDは省略)とプログラムは連載と同じです。
測定した実際の値を見ようと、メイン関数の172行目にブレークポイントを設定してデバッグ実行しても
actual_temp、actual_hum、actual_pressを見ることができません。
画像のように「Not Recognized」と表示されます。
ネットでも検索してみたのですが解決策を見つけられませんでした。
MPLABX v5.30
XC8 v2.10
PICKit3
Windows10 Pro/Home どちらも64bit
を使用しています。
幸い、変換前のcompensated_tempなどの値は見えるので、そちらでそれらしき値が取れていることは確認しました。
ブレークポイントの位置を変えてみたり、ステップ実行させてみたり、Watchesリストに入れてみたりと
分かる範囲でいろいろ試しているのですがダメです。

hyama
hyama
5 年 前

bme280ForcedMeasurement() の後 bme280ReadMeasuredRawData() より前に、
status regidter の measuring が 0 になるのを待った方が良くないでしょうか?

管理者
管理者
返信  hyama
5 年 前

ご指摘どうもありがとうございます。

確かに、1ショットの測定コマンドを送った場合、そこからAD変換を開始して測定結果のレジスタに格納するまでの時間がある程度かかるのでstatus registerが0になるのを待ったほうがよさそうです。読み出した測定結果のレジスタが前回測定したものなのか、今回測定したものなのかわからない、という状態になってしまいます。

プログラムとしては、statusレジスタを読み出した結果が0になるまで待つ、という方法が正攻法ですが、AD変換の時間はそれほど長くないと思いますので、__delay_ms()関数で時間待ちするのが手っ取り早いかもしれません。

この辺りは、また記事をアップデートするときに検討してみます。

貴重なご指摘をいただき、どうもありがとうございました!

目次