PicoMite（MMBasic）でBME280センサーを使う

# はじめに

秋月電子通商で購入した[BME280使用 温湿度・気圧センサーモジュールキット](https://akizukidenshi.com/catalog/g/g109421/)をPicoMite（MMBasic）で使えるようにChatGPTに問い合わせて生成させたコードを修正して動作するコードを作成しました。その内容などを以下に示します。

# 使用部品

使用した部品は秋月電子通商の[BME280使用 温湿度・気圧センサーモジュールキット](https://akizukidenshi.com/catalog/g/g109421/)AE-BME280です。
BME280はI<sup>2</sup>CまたはSPIで接続できます。今回はI<sup>2</sup>CでRaspberry Pi PicoおよびPicoCalcと接続しました。

# 配線

BME280をRaspberry Pi PicoとPicoCalcの両方で使用できるようにRaspberry Pi Picoとの接続を次のように決めた。PicoCalcで外部から接続できるI/OピンはGP2〜GP5とGP21とGP28の6ピンだけです。

|BME280のピン|Picoのピン（物理ピン）|
|:-:|:-:|
|VDD|3V3|
|GND|GND（3）|
|CSB|未接続|
|SDI|GP4（6）|
|SDO|GND|
|SCK|GP5（7）|

Raspberry Pi Picoでの実体配線図を以下に示します。


# プログラム

PicoMiteはBME280をサポートしていないため、自分で用意する必要があります。今回もChatGPTの`o4-mini-high`にコードを生成させました。それらしいコードを生成してくれましたがそのままでは動作しないものだったので手作業などで動作するように修正しました。

BME280を使った温度、湿度、気圧測定は次の手順で実行しています。

1. キャリブレーション（補正係数読み出し）
2. 測定モード設定
3. センサーから温度、湿度、気圧の生データ読み出し（全8バイト）
4. 生データから温度計算
5. 生データから気圧計算
6. 生データから湿度計算
7. 計算結果をOLEDディスプレイに表示

##  キャリブレーション（補正係数読み出し）

補正係数は16ビットおよび8ビットで符号なし、符号付きのデータです。それぞれのデータ形式に合わせてデータを組み上げている。
補正係数のデータは下表に示すレジスタから読み取り、データ形式に応じたデータを変数に保存している。

|項目|レジスタ範囲|キャリブレーション定数|
|-|-|-|
|温度|&H88〜&H8D|dig_T1〜dig_T3|
|気圧|&H8E〜&H9F|dig_P1〜dig_P9|
|湿度|&HA1、&HE1〜&HE7|dig_H1〜dig_H6|

- `I2C Write`コマンドで読み取るレジスタを指定
- `I2C Rread`コマンドで2バイト、または1バイトのデータを読み取る
- 係数ごとのデータ形式に応じて係数値を算出

コード量が多いので下記の全コードの51〜125行を参照

## 測定モード設定

センサ内部のADCの1測定サイクル中に温度、湿度、気圧測定を何回測定するか（オーバーサンプリング）とデバイスの動作速度、フィルタ、インターフェイスの設定をしている。

ここでは温度、湿度、気圧測定は測定サイクル中に1回測定、ノーマルモード動作、フィルタとSPIは使用しない設定にしています。

|レジスタ|名称|設定項目|
|:-:|-|-|
|&HF2|ctrl_hum|湿度データのオーバーサンプリング|
|&HF4|ctrl_meas|温度、気圧のオーバサンプリング、センサーモード|
|&HF5|config|ノーマルモードスタンバイ時間、フィルタ、SPI使用|

```
127	'---- オーバサンプリング、測定モード設定
128	Sub Setup
129	  I2C Write BME280_ADDR, 0, 2, &HF2, &H01   ' ctrl_hum: x1
130	  I2C Write BME280_ADDR, 0, 2, &HF4, &H27   ' ctrl_meas: temp x1, press x1, mode normal(3)
131	  I2C Write BME280_ADDR, 0, 2, &HF5, &H20   ' config:
132	End Sub
```

## センサーから温度、湿度、気圧の生データ読み出し

キャリブレーション、測定モードの設定が終わればセンサーから温度、湿度、気圧の生データを読み出せます。
センサーからの生データは8つのレジスタ（0xF7〜0xFE）に格納されているので先頭のレジスタを指定してバルク読み出しし、バッファの配列data%()に格納しています。

```
 30	  ' 生データのバルク読み出し (レジスタ0xF7～0xFE: 8バイト)
 31	  I2C Write BME280_ADDR, 0, 1, &HF7
 32	  I2C READ  BME280_ADDR, 0, 8, data%()
```

バッファの8バイトのデータは気圧で3バイト（press_msb、press_lsb、press_xlsb）、温度で3バイト（temp_msb、temp_lsb、temp_xlsb）、湿度で2バイト（hum_msb、hum_lsb）の順で格納されており、次の表に示す順でデータが並んでいます。

|レジスタ|アドレス|データ|ファッバ|
|:-:|:-:|:-:|:-:|
|press_msb|0xF7|press_msb<7:0>|data%(0)|
|press_lsb|0xF8|press_lsb<7:0>|data%(1)|
|press_xlsb|0xF9|press_xlsb<7:4>0000|data%(2)|
|temp_msb|0xFA|temp_msb<7:0>|data%(3)|
|temp_lsb|0xFB|temp_lsb<7:0>|data%(4)|
|temo_xlsb|0xFC|temp_xlsb<7:4>0000|data%(5)|
|hum_msb|0xFD|press_msb<7:0>|data%(6)|
|hum_lsb|0xFE|press_lxb<7:0>|data%(7)|

## 生データから温度計算

温度の生データは上の表に示したとおり、data%(3)からdata%(5)に上位ビットから順に格納されていてdata%(5)の下位4ビットを除いた20ビット分です。
プログラムの137行目のようにtemp_msbのdata%(3）の8ビットを左へ12ビットシフトして19〜12ビットに、temp_lsbのdata%(4)の8ビットを左へ4ビットして11〜4ビットに、temp_xlsbのdata%(5)を右へ4ビットシフトして3〜0にし、それらの値の論理和を求めると20ビットの生データになります。生データと補正データ使って温度データを計算します。

```
133	'---- 温度計算
134	Function get_temperature()
135	  Local var1, var2, raw_t%
136	  ' 生データ
137	  raw_t% = data%(3) << 12 Or data%(4) << 4 Or data%(5) >> 4
138	  ' 補正データから温度計算
139	  var1 =  (raw_t% / 16384.0  - dig_T1 / 1024.0)      * dig_T2
140	  var2 = ((raw_t% / 131072.0 - dig_T1 / 8192.0) ^ 2) * dig_T3
141	  t_fine = var1 + var2
142	  get_temperature = t_fine / 5120.0
143	End Function
```

## 生データから気圧計算

気圧の生データはdata%(0)からdata%(3)に温度データと同様に20ビット分のデータが格納されていて温度データとと同様に20ビットのデータへ変換し、補正データから気圧の値を計算している。

```
144	'---- 気圧計算
145	Function get_pressure()
146	  Local var1, var2, p, raw_p%
147	  ' 生データ
148	  raw_p% = (data%(0) << 12 Or data%(1) << 4 Or data%(2)) >> 4
149	  ' 補正データから気圧計算
150	  var1 = t_fine / 2.0 - 64000.0
151	  var2 = var1 * var1 * dig_P6 / 32768.0
152	  var2 = var2 + var1 * dig_P5 * 2.0
153	  var2 = var2 / 4.0 + dig_P4 * 65536.0
154	  var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0
155	  var1 = (1.0 + var1 / 32768.0) * dig_P1
156	  If var1 = 0 Then
157	    p = 0
158	  Else
159	    p = 1048576.0 - raw_p%
160	    p = (p - var2 / 4096.0) * 6250.0 / var1
161	    var1 = dig_P9 * p * p / 2147483648.0
162	    var2 = p * dig_P8 / 32768.0
163	    p = p + (var1 + var2 + dig_P7) / 16.0
164	  EndIf
165	  get_pressure = p
166	End Function
```

## 生データから湿度を計算

湿度の生データはdata%(6)とdata%(7)の16ビット分なのでdata%(6)を左へ8ビットシフトしdata%(7)との論理和で得られる。補正データから湿度を計算する。得られる値はPa単位になる。

```
168	' --- 湿度計算
169	Function get_humidity()
170	  Local hum, raw_h%
171	  ' 生データ
172	  raw_h% = data%(6) << 8 Or data%(7)
173	  ' 補正データから湿度計算
174	  hum = t_fine - 76800.0
175	  hum = (raw_h% - (dig_H4 * 64.0 + dig_H5 / 16384.0 * hum)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * hum * (1.0 + dig_H3 / 67108864.0 * hum)))
176	  hum = hum * (1.0 - dig_H1 * hum / 524288.0)
177	  If hum > 100 Then hum = 100
178	  If hum < 0   Then hum = 0
179	  get_humidity = hum
180	End Function
```

## 温度、気圧、湿度の表示

温度、気圧、湿度のそれぞれの値を得られたので桁数を揃えてコンソールとOLEDディスプレイに出力します。

```
 32	  temp = get_temperature()     ' 温度データ取得
 33	  pres = get_pressure() / 100  ' 気圧データ取得　hPa単位
 34	  humi = get_humidity()        ' 湿度データ取得
 35	  '--- 結果表示 ---
 36	  Print Time$
 37	  Print "Temperature: "; Str$(temp, 4, 1); "C"
 38	  Print "Pressure   : "; Str$(pres, 4, 1); " hPa"
 39	  Print "Humidity   : "; Str$(humi, 4, 1); " %"
 40	  Print
 41	  Text 0,  0, Time$
 42	  Text 0, 12, Str$(temp, 4, 1) + Chr$(&H60) + "C",,4
 43	  Text 0, 28, Str$(pres, 4, 1) + "hPa",,4
 44	  Text 0, 44, Str$(humi, 4, 1) + "%",,4
```

## プログラム全体

```BASIC:BME280.bas
  1	'----------------------------------------------------------
  2	' BME280 測定プログラム (MMBasic on Raspberry Pi Pico)
  3	' I2C bus#0, SDA=GP4, SCL=GP5 (100kHz)
  4	' I2Cアドレス 0x76
  5	'----------------------------------------------------------
  6	Option Explicit
  7	Dim t_fine = 0.0
  8	Dim data%(7)     ' 生データ保管
  9	Dim pres         ' 気圧
 10	Dim temp         ' 温度
 11	Dim humi         ' 湿度
 12	' 補正値データ
 13	Dim dig_T1, dig_T2, dig_T3
 14	Dim dig_P1, dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9
 15	Dim dig_H1, dig_H2, dig_H3, dig_H4, dig_H5, dig_H6
 16	
 17	Const BME280_ADDR = &H76
 18	SetPin GP4, GP5, I2C
 19	I2C OPEN 100, 100
 20	' SoftRest
 21	I2C Write BME280_ADDR, 0, 1, &HE0
 22	I2C Write BME280_ADDR, 0, 1, &HB6
 23	
 24	calibration
 25	Setup
 26	'--- 無限ループで測定
 27	Do
 28	  ' 生データのバルク読み取り
 29	  I2C Write BME280_ADDR, 0, 1, &HF7
 30	  I2C READ  BME280_ADDR, 0, 8, data%()
 31	
 32	  temp = get_temperature()     ' 温度データ取得
 33	  pres = get_pressure() / 100  ' 気圧データ取得
 34	  humi = get_humidity()        ' 湿度データ取得
 35	  '--- 結果表示 ---
 36	  Print Time$
 37	  Print "Temperature: "; temp; "C"
 38	  Print "Pressure   : "; pres; " hPa"
 39	  Print "Humidity   : "; humi; " %"
 40	  Print
 41	  Text 0,  0, Time$
 42	  Text 0, 12, Str$(temp, 4, 1) + Chr$(&H60) + "C",,4
 43	  Text 0, 28, Str$(pres, 4, 1) + "hPa",,4
 44	  Text 0, 44, Str$(humi, 4, 1) + "%",,4
 45	  Pause 10000   ' 1秒待機
 46	Loop
 47	
 48	I2C CLOSE
 49	End
 50	'---- calibrationデータの読み取り
 51	Sub calibration
 52	  Dim t%(1), l, h, th
 53	  ' 温度補正データ
 54	  I2C Write BME280_ADDR, 0, 1, &H88
 55	  I2C READ  BME280_ADDR, 0, 2, t%()
 56	  dig_T1 = t%(0) + t%(1) * 256
 57	  
 58	  I2C Write BME280_ADDR, 0, 1, &H8A
 59	  I2C READ  BME280_ADDR, 0, 2, t%()
 60	  dig_T2 = t%(0) + t%(1) * 256
 61	  If dig_T2 > 32767 Then dig_T2 = dig_T2 - 65536
 62	  
 63	  I2C Write BME280_ADDR, 0, 1, &H8C
 64	  I2C READ  BME280_ADDR, 0, 2, t%()
 65	  dig_T3 = t%(0) + t%(1) * 256
 66	  If dig_T3 > 32767 Then dig_T3 = dig_T3-65536
 67	  
 68	  ' 気圧補正データ
 69	  I2C Write BME280_ADDR, 0, 1, &H8E
 70	  I2C READ  BME280_ADDR, 0, 2, t%()
 71	  dig_P1 = t%(0) + t%(1) * 256
 72	  I2C Write BME280_ADDR, 0, 1, &H90
 73	  I2C READ  BME280_ADDR, 0, 2, t%()
 74	  dig_P2 = t%(0) + t%(1) * 256
 75	  If dig_P2 > 32767 Then dig_P2 = dig_P2-65536
 76	  I2C Write BME280_ADDR, 0, 1, &H92
 77	  I2C READ  BME280_ADDR, 0, 2, t%()
 78	  dig_P3 = t%(0) + t%(1) * 256
 79	  If dig_P3 > 32767 Then dig_P3 = dig_P3-65536
 80	  I2C Write BME280_ADDR, 0, 1, &H94
 81	  I2C READ  BME280_ADDR, 0, 2, t%()
 82	  dig_P4 = t%(0) + t%(1) * 256
 83	  If dig_P4 > 32767 Then dig_P4 = dig_P4-65536
 84	  I2C Write BME280_ADDR, 0, 1, &H96
 85	  I2C READ  BME280_ADDR, 0, 2, t%()
 86	  dig_P5 = t%(0) + t%(1) * 256
 87	  If dig_P5 > 32767 Then dig_P5 = dig_P5-65536
 88	  I2C Write BME280_ADDR, 0, 1, &H98
 89	  I2C READ  BME280_ADDR, 0, 2, t%()
 90	  dig_P6 = t%(0) + t%(1) * 256
 91	  If dig_P6 > 32767 Then dig_P6 = dig_P6-65536
 92	  I2C Write BME280_ADDR, 0, 1, &H9A
 93	  I2C READ  BME280_ADDR, 0, 2, t%()
 94	  dig_P7 = t%(0) + t%(1) * 256
 95	  If dig_P7 > 32767 Then dig_P7 = dig_P7-65536
 96	  I2C Write BME280_ADDR, 0, 1, &H9C
 97	  I2C READ  BME280_ADDR, 0, 2, t%()
 98	  dig_P8 = t%(0) + t%(1) * 256
 99	  If dig_P8 > 32767 Then dig_P8 = dig_P8-65536
100	  I2C Write BME280_ADDR, 0, 1, &H9E
101	  I2C READ  BME280_ADDR, 0, 2, t%()
102	  dig_P9 = t%(0) + t%(1) * 256
103	  If dig_P9 > 32767 Then dig_P9 = dig_P9-65536
104	  
105	  ' 湿度補正データ
106	  I2C Write BME280_ADDR, 0, 1, &HA1
107	  I2C READ  BME280_ADDR, 0, 1, dig_H1
108	  I2C Write BME280_ADDR, 0, 1, &HE1
109	  I2C READ  BME280_ADDR, 0, 2, t%()
110	  dig_H2 = t%(0) + t%(1) * 256
111	  If dig_H2 > 32767 Then dig_H2 = dig_H2-65536
112	  I2C Write BME280_ADDR, 0, 1, &HE3
113	  I2C READ  BME280_ADDR, 0, 1, dig_H3
114	  I2C Write BME280_ADDR, 0, 1, &HE4
115	  I2C READ  BME280_ADDR, 0, 1, l
116	  I2C Write BME280_ADDR, 0, 1, &HE5
117	  I2C READ  BME280_ADDR, 0, 1, h
118	  I2C Write BME280_ADDR, 0, 1, &HE6
119	  I2C READ  BME280_ADDR, 0, 1, th
120	  dig_H4 = l * 16 + (h And &HF)
121	  dig_H5 = th * 16 + Int(h / 16)
122	  I2C Write BME280_ADDR, 0, 1, &HE7
123	  I2C READ  BME280_ADDR, 0, 1, dig_H6
124	  If dig_H6 > 127 Then dig_H6 = dig_H6-256
125	End Sub
126	
127	'---- オーバサンプリング、測定モード設定
128	Sub Setup
129	  I2C Write BME280_ADDR, 0, 2, &HF2, &H01   ' ctrl_hum: x1
130	  I2C Write BME280_ADDR, 0, 2, &HF4, &H27   ' ctrl_meas: temp x1, press x1, mode normal(3)
131	  I2C Write BME280_ADDR, 0, 2, &HF5, &H20   ' config:
132	End Sub
133	'---- 温度計算
134	Function get_temperature()
135	  Local var1, var2, raw_t%
136	  ' 生データ
137	  raw_t% = data%(3) << 12 Or data%(4) << 4 Or data%(5) >> 4
138	  ' 補正データから温度計算
139	  var1 =  (raw_t% / 16384.0  - dig_T1 / 1024.0)      * dig_T2
140	  var2 = ((raw_t% / 131072.0 - dig_T1 / 8192.0) ^ 2) * dig_T3
141	  t_fine = var1 + var2
142	  get_temperature = t_fine / 5120.0
143	End Function
144	'---- 気圧計算
145	Function get_pressure()
146	  Local var1, var2, p, raw_p%
147	  ' 生データ
148	  raw_p% = data%(0) << 12 Or data%(1) << 4 Or data%(2) >> 4
149	  ' 補正データから気圧計算
150	  var1 = t_fine / 2.0 - 64000.0
151	  var2 = var1 * var1 * dig_P6 / 32768.0
152	  var2 = var2 + var1 * dig_P5 * 2.0
153	  var2 = var2 / 4.0 + dig_P4 * 65536.0
154	  var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0
155	  var1 = (1.0 + var1 / 32768.0) * dig_P1
156	  If var1 = 0 Then
157	    p = 0
158	  Else
159	    p = 1048576.0 - raw_p%
160	    p = (p - var2 / 4096.0) * 6250.0 / var1
161	    var1 = dig_P9 * p * p / 2147483648.0
162	    var2 = p * dig_P8 / 32768.0
163	    p = p + (var1 + var2 + dig_P7) / 16.0
164	  EndIf
165	  get_pressure = p
166	End Function
167	
168	' --- 湿度計算
169	Function get_humidity()
170	  Local hum, raw_h%
171	  ' 生データ
	172	  raw_h% = data%(6) << 8 Or data%(7)
173	  ' 補正データから湿度計算
174	  hum = t_fine - 76800.0
175	  hum = (raw_h% - (dig_H4 * 64.0 + dig_H5 / 16384.0 * hum)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * hum * (1.0 + dig_H3 / 67108864.0 * hum)))
176	  hum = hum * (1.0 - dig_H1 * hum / 524288.0)
177	  If hum > 100 Then hum = 100
178	  If hum < 0   Then hum = 0
179	  get_humidity = hum
180	End Function

```

## 実行結果

Raspberry Pi PicoとPicoCalcでの実行結果は次の通り。PicoCalcではコンソール出力は削除している。

![BME280-pico.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72921/f8720d13-9001-4cb2-91d6-6517d2ff82df.jpeg)

![BME280-picocalc.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72921/4795bf95-392f-4d3a-ab2e-49bb9e9c690d.jpeg)



# 参考サイト

- [BME280データシート](https://akizukidenshi.com/goodsaffix/BST-BME280_DS001-10.pdf)
- [PicoMite User Manual](https://geoffg.net/Downloads/picomite/PicoMite_User_Manual.pdf)