IoT演習第７回、I2C

# 1. はじめに
[IoT演習シラバス案その２（授業計画表）](https://qiita.com/takashiyamanoue/items/0cf9d58265bff5883698)の第7週に予定している内容の一部です。

マイクロコントローラ（マイコンと呼ぶこともあります）は、コンピュータ(CPU+メモリ)と入出力機能を、ICの１つのチップや１枚のボードにまとめたものです。マイコンには電源を供給する端子の他に入出力を行う端子がついています。マイコンの入出力端子に、情報を入力するセンサ、情報を出力する表示装置、動作を行うアクチュエータ（モータなど）を接続して、様々な物を制御することができます。

https://ja.wikipedia.org/wiki/マイクロコントローラ

今回は、マイクロコントローラのI2Cインターフェースの入出力を使ったI2C周辺素子(I2Cセンサ、I2Cアクチュエータなど)の利用について説明します。内容は以下のとおりです。

- I2Cの説明
- I2Cセンサの接続と接続確認
- I2Cセンサを使った温度・湿度・気圧計の作成の演習
- 授業の振り返り

# 2. I2Cの説明

I2C（アイ・スクエアド・シー、アイ・アイ・シー）はフィリップス社で開発されたシリアルバスです。

シリアルバスのシリアル（シリアル通信）とは、送信側ではHigh(1)とLow(0)を時間変化に合わせて交互に送信し、受信側ではそれを受信することで通信を行うものです。モールス信号による送受信はシリアル通信の一種です。シリアルバスとは、複数の装置が１本または複数本の導線を共有することで、２個以上の装置の間で通信を可能にするものです。

低速な周辺機器をマザーボードへ接続したり、組み込みシステム、携帯電話などで使われています。

https://ja.wikipedia.org/wiki/I2C

I2Cは以下の特徴を持っています。

- 少数の本数の線で構成されたバスをデバイス間で共有して入出力を行います
- 信号線はクロックを与える SCLと入出力を与える SDAの２本を使います
- SCLとSDAのどちらも、適度なプルアップ抵抗が必要になります。（但し、Pico WのSCL端子もSDA端子も、Pico Wの内部でプルアップされています）
- SCLもSDAも, I2Cに接続されたどのデバイスも 0を出力していないときは、すべてのデバイスの出力は Z (High impedance)になります
- すべてのデバイスの出力が Z のとき、1(High)となるように、SDAとSCLにはプルアップ抵抗が接続されています。抵抗値は接続されるデバイスの数、バスの長さ、通信速度などに応じて適宜調整します
- 信号線の他にGNDと電源の線（合計４本の線）が必要になります
- （基本的には）１台のMaster device（マスターデバイス） と複数台の Slave devices（スレーブデバイス） が2本の信号線（ともう２本の電源線）に接続され、マスターデバイスがスレーブデバイスを制御します
- 各デバイスには、あらかじめ、それぞれ（重ならない）7bit のアドレスが付けられています
    - 送信信号の最初の8bitには、アドレスを表す7bit の後ろに、書き込みを表す0か, 読み出しを表す1が続きます
- 1つのSlave device にはレジスタが通常複数個格納されていて、そのレジスタに値を書き込んだり、レジスタの値を取り出したりすることにより、Master device が Slave devices を制御します
- Master と Slave の間のデータ交換は基本的には１バイトずつ行われます
- 通信開始の手順と通信終了の手順が決まっています

![i2c-bus-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/4312f631-6aa3-b3be-2665-c9f391ffa1e1.jpeg)

http://www.picfun.com/c15.html

マイクロコントローラでI2C周辺素子を使うとき、マイクロコントローラをI2Cのマスターデバイス、I2C周辺素子をI2Cのスレーブデバイスとし、SCL、SDAの0/1を切り替えながら通信が行われます。しかしながら、これを直接利用者のプログラムで行うのは大変なので、多くの場合、マイクロコントローラのライブラリが用意されていて、利用者はライブラリの関数を呼び出すことで、I2C周辺素子を利用できるようになっています。



# 3. I2Cセンサの接続と接続確認

Pico WのGPIOの多くは、I2C通信のSDLまたはSDA信号端子として使うことができます。以下はPico Wの端子の機能割り当てを書いた図です。

![20240623-pico-w-pinout.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/820b1ced-5bff-4298-94b8-4f2445beac3d.png)

この演習では、温度・湿度・気圧を計測することができるI2Cセンサボード、AE-BME280 を使って、温度・湿度・気圧を計測することにします。
まず、AE-BME280をPico Wに接続し、I2C通信ができることを確認します。

https://akizukidenshi.com/catalog/g/g109421/

## 3.1 ハードウェア

秋月のボード、[AE-BME280](https://akizukidenshi.com/catalog/g/g109421/)、のJ3を半田でブリッジ（ショート）し、I2Cの動作モードにします(演習で使うAE-BME280ではこの加工は済んでいる予定です)。ブリッジしなくても、AE-BME280のCSB端子(VDDを左に置いた場合、左から３番目の端子)を3.3Vに接続(VDDとCSBをショート）すればOKです。AE-BME280ボードのSDIと表示がある端子が、I2Cの動作モードの時のSDA端子になります。同じくSCKと表示がある端子が、SCL端子になります。

BME280は0x76か0x77のどちらかのI2Cアドレスを持ちます。SDO端子をどこにも接続しない場合には、0x76となり、SDO端子に3.3Vをかけると、0x77番地になります。

Pico Wの端子の機能割り当ては以下のようになっています。1番ピンにI2C0 SDA、2番ピンにI2C0 SCLが割り当てられていることがわかります。

![DSC_2047.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/f94fe293-75a2-d3dc-ab15-9060a2a13cf9.jpeg)

AE-BME280とPico Wをブレッドボードとジャンプワイヤを使って以下のように接続します。

このとき、下の写真のブレッドボードの一番左の赤い線のついた縦方向の穴の列は、I2CバスのSDA信号線として使っています。その隣の青い線のついた縦方向の穴の列は、I2CバスのSCL信号線として使っています。このように決めたI2Cバスを使って、SDA端子同士とSCL端子同士を接続しています。

ブレッドボードの右側の赤い線のついた縦方向の穴の列は、3.3Vの電源として使っています。もっとも右の青い線のついた縦方向の穴の列は、GNDとして使っています。この、右側の電源の穴の列とGNDの穴の列を使って、AE-BME280のVDD端子に3.3Vを供給するとともに、AE-BME280のGND端子をGND端子と接続しています。



![DSC_2046.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/1706e38e-e9a2-4e6a-5875-6eb97c2a5dd0.jpeg)


![20240915-bme280-bbd-sch-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/dc6a01a0-8b0a-255e-021a-01e6f53866a1.png)

これで配線（ハードウェア）ができました。

## 3.2 I2C通信ができることの確認

(以下は、[第２回](https://qiita.com/takashiyamanoue/items/4baba8e23c86dc07962d)で説明している、Anaconda(Python), VSCode, VSCodeのMicroPicoのExtentionがインストール済みであることを前提としています。)


3.1で作った回路でI2C通信ができることを確認するため、Pico WをUSBでPCに接続し、以下のプログラムを実行します。通信がうまくできたら、I2Cバス(Pico Wは2つのI2Cを同時に使うことができるのですが、その0番目のI2Cバス)に接続されたスレーブデバイスのアドレスが16進で表示されます。
１つのI2Cバスには複数のスレーブデバイスを接続することができます。もし、BME280以外の、アドレスが異なるデバイスが接続されていれば、そのデバイスのアドレスも表示されます。

```Python
import machine
sda=machine.Pin(0)
scl=machine.Pin(1)
i2c=machine.I2C(0,sda=sda, scl=scl, freq=100000)
addrs=i2c.scan()
for addr in addrs:
    print('{:#04x}'.format(addr)) 
```

ここで、
```Python
i2c=machine.I2C(0,sda=sda, scl=scl, freq=100000)
```
は、0番目のI2C通信バスを操作するインスタンス i2cを作っています。
sda=sdaは、sda信号の入出力端子を、この行の上で作ったsdaインスタンス(macine.Pin(0))に割り当て、scl=sclは、scl信号の入出力端子を、この行の上で作ったsclインスタンス(machine.Pin(1))に割り当てていることを表します。freq=100000は、I2Cバスの周波数を100KHzにすることを表しています。この場合、最大通信速度は、100Kbit/secになります。

```Python
addrs=i2c.scan()
```
はi2cインスタンスが表すI2Cバスに接続されているI2Cスレーブデバイスを探し出し、そのアドレスの配列をaddrsに代入しています。

```Python
for addr in addrs:
    print('{:#04x}'.format(addr)) 
```
は、配列 addrsに入っている、i2cバスに接続されたスレーブデバイスのアドレスを、ひとつづつ、16進表示で出力することを表しています。

上のプログラムを実行すると以下のような表示がREPL端末に表示され、AE-BME280(スレーブアドレス0x76)が接続されていることがわかります。

![20240915-bme280-scan-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/0192e728-7c30-db1f-6baa-388196ab7bbe.png)

## 3.3 演習
3.1, 3.2を実際にやってみてください。


# 4. I2Cセンサを使った温度・湿度・気圧計の作成の演習

# 4.1 ハードウェア
この演習のハードウェアは 3.1で説明したものと同じものを使います。


# 4.2 ソフトウェア

BME280をMicro Pythonで簡単に使うために、以下のライブラリが公開されています。これを使わせていただくことにします。

https://github.com/robert-hh/BME280/blob/master/bme280_int.py


[第２回](https://qiita.com/takashiyamanoue/items/4baba8e23c86dc07962d)の「7. Visual Studio Code (VSCode) の起動」の説明以降に従って、例えば"bmetest2.py"という名前のファイルを新たに作成し、ここにプログラムを書くことにします。bmetest2.pyのファイルを作った後、上のリンク先のプログラム(bme280_int.py)をコピーし、bmetest2.pyに貼り付けます。

bmetest2.pyの、bme280_int.pyを張り付けた部分の後ろに、以下のプログラムを付け足します。

```Python
import machine
sda=machine.Pin(0)
scl=machine.Pin(1)
i2c = machine.I2C(0, sda = sda, scl = scl, freq = 100000)
bme = BME280(i2c = i2c)
print(bme.values[0])
```
上のプログラムの中で、
```Python
bme=BME28(i2c=i2c)
```
は、そのうえで作成したi2cインスタンスに接続された、i2cセンサデバイスBME280を操作するインスタンスbmeを作ることを表します。bme280のi2cのアドレスは、3.で調べた結果0x76であることがわかっていますが、bme280_int.py の48行目に、
```Python
BME280_I2CADDR = 0x76
```
の定義があり、また、68行目からはじまるBME280クラスの定義の、コンストラクタの定義で、
```Python
    def __init__(self,
                 mode=BME280_OSAMPLE_8,
                 address=BME280_I2CADDR,
                 i2c=None,
                 **kwargs):
 
```
のように、addressをBME280_I2CADDRESS、すなわち、0x76としているので、
プログラムを書き換えずに、そのまま使うことにします。


```Python
print(bme.values[0])
```
のbme.valuesは、BME280が取得した温度、気圧、湿度の値を、それぞれの単位を付けた文字列として格納した配列を表します。bme.values[0]は温度、bme.values[1]は気圧、bme.values[2]は湿度です。

このプログラムを実行すると、以下のように、温度が表示されます。

![20240915-bme280-temp-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/b1052cc4-76ad-0989-851c-4ef3926b1c43.png)



上のプログラムの、print(bme.values[0])の 0 を １にすると、気圧が表示されます。

![20240915-bme280-pressure-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/a9b47933-71af-6146-e7ad-302554791e04.png)

2にすると、湿度が表示されます。

![20240915-bme280-humid-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585767/cb20d212-9145-7678-01a4-546521435bf1.png)

# 4.3 演習
4.2 を実際にやってみてください。