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

温湿度・気圧センサーBME280のデータシートを参考にして、温湿度・気圧データの取得プログラムを作成します。

目次

測定データ取得の流れ

最初に、温湿度・気圧センサーBME280の測定データ取得の流れを確認します。

この図のように電源投入後、「❶ 動作設定」を行い、「❷ 構成用データ取得」を行います。この2つの処理は電源投入後1回だけ行います。

次に測定の度に、「❸ 測定指示」を行い、「❹ 測定データ取得」をして、最後にプログラムで「❺ 測定データの校正」を行います。


このあとのセクションでは、これらの処理ステップごとにプログラムを解説します。

なお、データ取得のプログラムはほとんどがデータシートを参考にして作成することになります。

そのため、データシートは英文なので、読み解くのが結構大変な作業です。心が折れそうになったときは、前に進む気持ちが一番大切になってきます。

なんとかプログラムを作成していきましょう!

関数の追加

ところでセンサーからデータを読み取る方法ですが、すでに指定したアドレスの1バイトデータを読み取る関数は作成済みです。

ただ、実際のデータ読み取りは2バイトのケースもありますので、2バイトデータの読み取り関数も作成しておきます。

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

追加する関数のプログラム

2バイトデータの読み取り関数

2バイトデータの読み取り関数を作成しておきます。

読み取りたいデータのアドレスを送信するとそのアドレスの1バイトのデータを読み取ることができますが、さらに続けてクロックを発生すると次のアドレスのデータを読み込むことができます。

なお、取得した2バイトデータは、最初の1バイトが上位バイト、次の1バイトが下位1バイトになります。

1バイト読み込み関数を流用して以下のように作成しました。

//
// 指定アドレスから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です。

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

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

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

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

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

これで準備ができましたので、次の順番でプログラムを作成していきます。

  • 動作設定
  • 校正用データ取得
  • 測定指示
  • 測定データ取得
  • 測定データ校正

❶ 動作設定のプログラム

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

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

動作設定のメモリはこのようにビット単位で細かく分かれています。

それぞれの設定値を詳しく説明すると長くなってしまいますので、必要な内容をなるべく簡潔に説明します。

t_sb(スタンバイ時間)

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

ひとつは、一定の時間間隔で連続して測定を行うモードです。例えば500ms毎に測定する、などです。

この場合、センサは自分から500ms毎に継続して測定を行います。

PICマイコン側は任意のタイミングでデータを取得でき、そのとき取得できるデータは最新の測定したデータになります。

このセンサでは、この時間間隔のことを「スタンバイ時間」と呼んでいます。

もうひとつは、普段はスリープモードになっていて、PICマイコンから測定指示した時に測定するモードです。

PICマイコンから測定指示をすると、このセンサはスリープモードから動作モードに移行して、温湿度・気圧を測定後、すぐにスリープモードに戻ります。

なお、スリープモードでも測定したデータの取得はできます。

今回はこのモードで測定を行います


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

設定値測定時間間隔(ms)
0000.5
00162.5
010125
011250
100500
1011000
11010
11120

filter(フィルタ)

どのような電子部品もノイズの影響を受けます。

そのため同じ環境下、例えば気圧が全く変わらない室内で測定した場合でも、測定値は若干のばらつきがあります。

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

このfilter機能では、指数移動平均線に近い計算を行なっています。

このfilter設定の設定値は、過去データをどのぐらい重要視するか、という意味で、数値が大きいほど過去のデータを重視してノイズの影響をより低減することができます。

ただ数値が大きいほど今回の測定値はすぐに反映されなくなりますので、測定値自体の精度は落ちます。

設定値設定内容
000OFF
0012
0104
0118
上記以外16

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

今回はOFFで使用します。

spi3w_en(3本線通信指定)

今までのSPI通信手順の解説では、通信線は4本で説明してきましたが、実際には3本線を使用する通信手順もあります。

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

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

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

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

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

先ほどのfilter設定は、ノイズの影響を低減するために「測定値」の平均値を取るものでした。

ノイズを低減する方法として、他の手法があります。

温湿度・気圧センサー自体はアナログ値で測定するため、センサー内部ではADコンバータを使用してデジタル値に変換しています。

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

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

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

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

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

mode(動作モード)

BME280の測定モードを指定します。

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

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

設定値モード
00スリープモード
011回測定後、スリープモードに戻る
10
11連続測定モード

以上の内容から、設定値を書き込むプログラムを作成します。

設定値は次のようにしました。(データシートの推奨値にしています)

設定項目設定値内容
t_sb0測定時間間隔の設定はしないため0を設定
filter0フィルタは使用しない
spi3w_en03線式は使用しない(4線式を使用)
osrs_t
osrs_h
osrs_p
1オーバーサンプリングはx1
mode0スリープモード

これまでの内容をプログラムにまとめます。

//
// 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();
}

❷ 補正データ取得のプログラム

ここからはデータシートに従ってプログラムを作成することになります。

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

測定データを取得した後、校正用データで校正する必要があります。

校正用データはセンサ個体ごとに異なりますが、その個体の校正用データは固定で変わりません。

そのため、電源投入後に1度取得したら、そのまま保存しておきます。また、校正用データはグローバル変数で宣言しておきます。

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

補正データはcalib00 〜 calib41の合計42バイトあります。

この42バイトですが、データシートでは「以下のように変数に読み込んでおいてください」と説明がありますので、この通りにします。(仕組みが知りたいところですが、解説はないのでこの通りにします)

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

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

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

という表記は、「0x88番地のデータをdig_T1の第7ビット〜第0ビットに格納し、0x89番地のデータをdig_T1の第15ビット〜第8ビットに格納する」という意味になります。 

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

// 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);
}

❸ 測定指示のプログラム

測定指示を行うには、動作設定modeの値を0b01に設定すればOKです。

なお、測定指示をしてから測定が完了するまで数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);
    
    // この段階でBME280の温湿度大気圧レジスタに測定値が格納されている
    // このあとBME280はスリープモードに入るが、温湿度・気圧データの読み取りは可能
}

❹ 測定データ取得のプログラム

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

SPI通信でこれらのメモリアドレスのデータを読み取ります。

アドレスが連続していますので、最初のアドレス(0xF7)を指定したあと、一気に8バイトを読み取ることにします。

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

校正前のデータは生データですので、変数名にrawをつけています。

// 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];
}

❺ 測定データ校正のプログラム

最後に、取得した生データを校正用データで校正するプログラムです。

校正方法は、データシートに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;
}

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

// 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);
}

最後は、気圧の補正関数ですが、データシートには最初に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;
}

さてと、困りました。

このままMPLAB X IDE(XC8)でビルドするとエラーになりますので、一つ一つ解決していきましょう。


まず、見慣れない変数型があります。BME280_S32_tBME280_U32_tなどです。

これらはデータシートに説明されていて「BME280_S32_tは符号付32ビット、BME280_U32_t符号なし32ビット変数型とのことです。(「S」がsigned、「U」がunsignedの意味で、数字がビット数ですね)

これらは、int32_tuint32_tなどに置き換えます。

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

これらの情報をもとに、補正プログラムは以下のように作成しました。

関数の中でt_fineという変数が出てきますが、int32_t型のグローバル変数で宣言しています。

また、サンプルプログラムでは、補正前のデータを引数にしていますが、作成するプログラムではグローバル変数にしていますので、引数はありません。

// グローバル変数
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);
}

なお、上の関数で補正した値は、そのまま実際の温湿度・気圧表示に使用できません。

「湿度60%」や「気温25度」というように、人が読む値にするには次の変換をする必要があります。

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

メイン関数の作成とデバッグ用の対応

これまで作成した関数を組み合わせれば、温湿度・気圧センサーの測定ができます。

メイン関数では、最終的には次に宣言する変数に実際の温湿度、気圧の数値を代入することにします。

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

動作確認するには、デバッグ機能でこれらの変数の値を確認することになります。

ただ、これらの変数に値を入れただけで、そのあと何も処理しない場合、これらの変数はコンパイラの最適化処理により、メモリ上に保存場所が確保されない変数になってしまいます。

この対策方法はいくつかありますが、今回は次のように変数の前にvolatileという修飾子をつけて、これらの変数が最適化されないようにします。

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

プログラムまとめ

最後に、温湿度・気圧データを取得するプログラムにまとめます。

データ取得、補正後にLEDを点滅させる処理を入れて、2秒に1回測定するようにしています。

/*
 * BME280温湿度大気圧センサー
 * 測定データ取得プログラム
 */

//
// 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();

    // 校正後のデータ
    int32_t  compensated_temp;
    uint32_t compensated_hum;
    uint32_t compensated_press;

    // 実際の温湿度(人が読む場合の数値)
    volatile float actual_temp;
    volatile float actual_hum;
    volatile float actual_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);
    
    // この段階でBME280の温湿度大気圧レジスタに測定値が格納されている
    // このあと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);
}


//
// 指定アドレスから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バイト読み込み
// 
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;

}


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

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

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

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

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

//
// 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;
    }

}

//
// 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です。

湿度は、冬であれば30%〜50%程度、夏であれば60%〜80%程度であればだいたい合っていると思います。

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

まず、気象庁のアメダスのサイトで一番近い地点の気圧を調べます。

最初に次の気象庁アメダスのサイトにアクセスします。

気象庁アメダス

次のような画面が表示されますので、「都道府県選択」のメニューからお住まいの地域を選択します。

例えば、神奈川県を選択すると、次のように神奈川県内の測定情報が表示されます。

全ての地域に気圧データがあるわけでありませんので、一番近くの地点の気圧を確認してください。

表の中に気圧データがありますので、その値を確認します。


このサイトで確認できるデータは、海抜0mの気圧です。

上の表では「海面気圧」と書かれていますが、海面、つまり海抜0mの気圧、という意味になります。

気圧は10m高くなると約1hPa低くなりますので、例えば海抜が高い地点や、ビルの高層階で測定している場合は補正が必要になります。

例えば海抜70m地点で測定する場合、アメダスの気圧データから7hPa程度低くなります。

そこで、標高が高い地点で計測している場合、次の手順で測定地点の標高を確認してみてください。

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

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

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

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

十字地点の標高はウィンドウの左下に表示されます。

なお、建物の高いところで計測する場合は、この標高よりも高くなりますので、だいたいの高さを足してみてください。


ところで、、、

プログラムがかなり複雑になってきましたよね。SPI通信部分はそれほど難しくないと思いますが、センサを制御するプログラム部分が難しいところだと思います。

ただ、センサ制御の部分はデータシートに従って作成する、という感じですのでプログラミングの難しさより、データシートを読んで理解する方がかなり難しいです。

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

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

更新履歴

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

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

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

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

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

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

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

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

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

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

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

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

初学者
初学者
5 年 前

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

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

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

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

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

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

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

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

Guest
Guest
5 年 前

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

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

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

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

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

第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

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

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

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

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

void main(void) {
char a;

while( true ) {
}

}

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

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

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

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

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

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

すみません、実際の動作を未確認ですが、コンパイラの最適化を避ける場合、「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
6 年 前

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

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

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

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

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

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

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

目次