ラズパイPicoのI2Cは、I2C0とI2C1の2系統あります。
使用するピンのペアはそれぞれ6組、計12組あります。
I2C0: [GP0/GP1]、[GP4/GP5]、[GP8/GP9]、 [GP12/GP13]、[GP16/GP17]、[GP20/GP21]
I2C1: [GP2/GP3]、[GP6/GP7]、[GP10/GP11]、[GP14/GP15]、[GP18/GP19]、[GP26/GP27]
このピンアウト図は https://datasheets.raspberrypi.org/pico/Pico-R3-A4-Pinout.pdf
からダウンロードしたものを、ロゴの位置をずらすなど少し加工しました。
この記事の概要
Pico の開発言語・環境は色々あります。
・Python(MicroPython, CircuitPython)
・C/C++
・Arduino IDE(earlephilhower版)
・Arduino IDE(Mbed OS RP2040版[公式版])
この記事は、Arduino IDEのMbed OS RP2040版でのI2Cの使い方を紹介します。(*1)
【1】I2Cを1系統のみ、デフォルトのピンペアで使用する
【2】I2Cを1系統のみ、ピンペアを選択して使用する
【3】2系統のI2Cを同時に使用する
(*1) Arduino IDEのearlephilhower版では、2系統のI2Cのピンの指定が簡単にできるようですが、公式版のMbed OSの方はピンの使い方全般(GPIO,ADC,I2C,SPI,UART)の情報が極めて少ないです。[日本語は特に。2021年秋現在]
【1】I2Cを1系統のみ、デフォルトのピンペアで使用する
SDAはGP6 (Picoの9ピン)、SCLはGP7 (Picoの10ピン)に接続します。
ピンアウト図ではデフォルトとしてGP4/GP5が濃い青色で表示されてますが、最初これに騙されてかなり悩みました。I2C0系統のピンペアではなくI2C1系統のGP6/GP7であることも謎です。なんでこんな中途半端なピンなの?普通、番号若い方がデフォルトにするのが常識だろうが(怒)。
しかし、これはピンアウト図が間違っているのではなく、Arduino IDEで開発する場合はPython等とは違う何らかの理由があってこのようにしたのかなと思います。
原因が分かりました!
pins_arduino.h というファイルの中で、以下のような順でピン番号を定義しています。
#define PIN_SERIAL_TX (0ul)
#define PIN_SERIAL_RX (1ul)
// SPI
#define PIN_SPI_SCK (2u)
#define PIN_SPI_MOSI (3u)
#define PIN_SPI_MISO (4u)
#define PIN_SPI_SS (5u)
// Wire
#define PIN_WIRE_SDA (6u)
#define PIN_WIRE_SCL (7u)
(ん? シリアルのデフォルトピンは GP0/GP1、SPIは GP2~GP5 だ! メモメモ・・・)
[注]RP2040 ライブラリをアップデート (2.2.0 ⇒ 3.4.1) したら pins_arduino.h のピン定義が変更されてました。(2023年11月)
昨年製作したコードを流用して別のものを作ろうとしたのですが、動作しないので調査したところ、ピン番号の定義が変更されたことが判明しました。シリアルは以前と変わりませんが、SPIとI2Cは変わってます。
// Serial
#define PIN_SERIAL_TX (0ul)
#define PIN_SERIAL_RX (1ul)
// SPI
#define PIN_SPI_MISO (16u)
#define PIN_SPI_MOSI (19u)
#define PIN_SPI_SCK (18u)
#define PIN_SPI_SS (17u)
static const uint8_t SS = PIN_SPI_SS; // SPI Slave SS not used. Set here only for reference.
static const uint8_t MOSI = PIN_SPI_MOSI;
static const uint8_t MISO = PIN_SPI_MISO;
static const uint8_t SCK = PIN_SPI_SCK;
// Wire
#define PIN_WIRE_SDA (4u)
#define PIN_WIRE_SCL (5u)
pins_arduino.h は下記2つのディレクトリにあります。内容は同じでした。
C:\Users\user\Documents\ArduinoData\packages\arduino\hardware\mbed_rp2040\3.4.1\variants\RASPBERRY_PI_PICO
C:\Users\user\AppData\Local\Arduino15\packages\arduino\hardware\mbed_rp2040\4.0.8\variants\RASPBERRY_PI_PICO
3つのスケッチを作って動作を確認します。
① I2CScannerでI2Cアドレスを調べる
② OLEDに文字を表示する
③ BME680 の測定値をシリアルモニタに表示する
ここでは概要のみ、さらっと紹介します。詳しくは下記の記事をご覧下さい。
【関連記事】
【1-1】I2CScannerでI2Cアドレスを調べる
I2Cのデバイスを動作させるには正確なI2Cアドレスを知る必要があります。アドレスがジャンパで切替え可能になっていて違うアドレスを指定したり、説明書のアドレス表記方法を勘違い(読み違い)すると動きません。こういう失敗を防ぐには、I2Cアドレス・スキャナでアドレスを確認すると良いです。
[詳細は、上記の関連記事を参照]
【1-2】OLEDに文字を表示する
[詳細は、上記の関連記事を参照]
Adafruitのライブラリのインストール、スケッチの書き込み手順などを分かり易く説明しています。
【1-3】BME680 の測定値をシリアルモニタに表示する
[詳細は、上記の関連記事を参照]
ライブラリのインストール、スケッチの書き込み手順に加え、秋月電子通商とストロベリーリナックスのBME680モジュールの違い、使用上の注意点も書きました。
【2】I2Cを1系統のみ、ピンペアを選択して使用する
例1:OLED
【1-2】項で使用したスケッチを少しだけ変更します。
#include <Wire.h> // I2C
#include <Adafruit_GFX.h> // グラフィックのコア・ライブラリ
#include <Adafruit_SSD1306.h> //
Adafruit_SSD1306 OLED(128, 64, &Wire, -1);
void setup() {
Wire.begin(); // I2Cを初期化
以下、省略
上記の4行目を以下の2行に置き換えます。SDA:GP26, SCL:GP27 を使用する例です。
Wire.begin(); はコメントアウトします(これ重要!)。
MbedI2C myi2c(p26,p27); // I2C SDA:GP26, SCL:GP27
Adafruit_SSD1306 OLED(128, 64, &myi2c, -1);
void setup() {
// Wire.begin(); // I2Cを初期化
以下、省略
変更後のスケッチです。
ピンペアを変更して、12組のどの組み合わせでも動作することを確認してみましょう。
#include <Wire.h> // I2C
#include <Adafruit_GFX.h> // グラフィックのコア・ライブラリ
#include <Adafruit_SSD1306.h> // OLED ライブラリ
//MbedI2C myi2c(p0,p1); // I2C SDA:GP0, SCL:GP1
//MbedI2C myi2c(p2,p3); // I2C SDA:GP2, SCL:GP3
//MbedI2C myi2c(p4,p5); // I2C SDA:GP4, SCL:GP5
//MbedI2C myi2c(p6,p7); // I2C SDA:GP6, SCL:GP7
//MbedI2C myi2c(p8,p9); // I2C SDA:GP8, SCL:GP9
//MbedI2C myi2c(p10,p11); // I2C SDA:GP10, SCL:GP11
//MbedI2C myi2c(p12,p13); // I2C SDA:GP12, SCL:GP13
//MbedI2C myi2c(p14,p15); // I2C SDA:GP14, SCL:GP15
//MbedI2C myi2c(p16,p17); // I2C SDA:GP16, SCL:GP17
//MbedI2C myi2c(p18,p19); // I2C SDA:GP18, SCL:GP19
//MbedI2C myi2c(p20,p21); // I2C SDA:GP20, SCL:GP21
MbedI2C myi2c(p26,p27); // I2C SDA:GP26, SCL:GP27
//Adafruit_SSD1306 OLED(128, 64, &Wire, -1);
Adafruit_SSD1306 OLED(128, 64, &myi2c, -1);
// 4つ目の引数はRESETピン: -1はArduinoのRESETピンと共有
//-----------------------------------------------------------------
void setup() {
digitalWrite(LED_BUILTIN, HIGH); // 内蔵LEDを点灯
// Wire.begin(); // I2Cを初期化
OLED.begin(SSD1306_SWITCHCAPVCC, 0x3C); // 内部チャージ回路をON
OLED.clearDisplay();
OLED.display();
OLED.setTextSize(1); // フォントサイズを1~で指定。
OLED.setTextColor(WHITE); // フォント色
OLED.setCursor(0, 0); // テキスト表示する座標
OLED.print("Raspberry Pi Pico"); // テキスト
OLED.setCursor(0, 8);
OLED.print("OLED Display");
OLED.setCursor(10, 16);
OLED.print("128 x 64");
OLED.display(); // テキストを表示
delay(2000);
}
//-----------------------------------------------------
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // 内蔵LEDを点灯
delay(300);
digitalWrite(LED_BUILTIN, LOW); // 内蔵LEDを消灯
delay(700);
}
例2:BME680の測定値をシリアルモニタに表示する
【1-3】項で使用したAdafruit BME680 ライブラリのスケッチ、[ファイル]⇒[スケッチ例]⇒[Adafruit BME680 Library]⇒[bme680test]をベースに変更します。
Adafruit_BME680 bme; // I2C
上記の1行を下記の2行に置き換えます。SDA:GP26, SCL:GP27 を使用する例です。
//Adafruit_BME680 bme; // I2C
MbedI2C myi2c(p26,p27); // I2C SDA:GP26, SCL:GP27
Adafruit_BME680 bme(&myi2c); // I2C
ピンペアを変更して、12組のどの組み合わせでも動作することを確認してみましょう。
【3】2系統のI2Cを同時に使用する
スケッチは、BME680 ライブラリの中にあるものを使用しますが、その前に下記フォルダにある pins_arduino.h を編集します。\mbed_rp2040\の次のバージョンは適宜読み替えてください。
C:\Users\user\Documents\ArduinoData\packages\arduino\hardware\mbed_rp2040\2.2.0\variants\RASPBERRY_PI_PICO\
[ちゅ]編集する前に、元のファイルはコピーしてファイル名を pins_arduino.h-ORIGINAL のように変更して保存しておきましょう。元通りの内容に戻す際は、いじった方のファイルは例えば pins_arduino.h-DUAL-I2C のような名前で保管します。オリジナルの方は内容を絶対に変更せず、ファイル名だけを変更するようにします。
■■■ 変更前 ■■■
// Wire
#define PIN_WIRE_SDA (6u)
#define PIN_WIRE_SCL (7u)
#define SERIAL_HOWMANY 1
#define SERIAL1_TX (digitalPinToPinName(PIN_SERIAL_TX))
#define SERIAL1_RX (digitalPinToPinName(PIN_SERIAL_RX))
#define SERIAL_CDC 1
#define HAS_UNIQUE_ISERIAL_DESCRIPTOR
#define BOARD_VENDORID 0x2e8a
#define BOARD_PRODUCTID 0x00c0
#define BOARD_NAME "RaspberryPi Pico"
uint8_t getUniqueSerialNumber(uint8_t* name);
void _ontouch1200bps_();
#define SPI_HOWMANY (1)
#define SPI_MISO (digitalPinToPinName(PIN_SPI_MISO))
#define SPI_MOSI (digitalPinToPinName(PIN_SPI_MOSI))
#define SPI_SCK (digitalPinToPinName(PIN_SPI_SCK))
#define WIRE_HOWMANY (1)
#define I2C_SDA (digitalPinToPinName(PIN_WIRE_SDA))
#define I2C_SCL (digitalPinToPinName(PIN_WIRE_SCL))
(以下、省略)
先頭の3行と最後の3行を下記のように変更・追加します。
■■■ 変更後 ■■■
// Wire
//#define PIN_WIRE_SDA (6u)
//#define PIN_WIRE_SCL (7u)
#define PIN_WIRE_SDA (16u)
#define PIN_WIRE_SCL (17u)
#define PIN_WIRE_SDA1 (18u)
#define PIN_WIRE_SCL1 (19u)
#define SERIAL_HOWMANY 1
#define SERIAL1_TX (digitalPinToPinName(PIN_SERIAL_TX))
#define SERIAL1_RX (digitalPinToPinName(PIN_SERIAL_RX))
#define SERIAL_CDC 1
#define HAS_UNIQUE_ISERIAL_DESCRIPTOR
#define BOARD_VENDORID 0x2e8a
#define BOARD_PRODUCTID 0x00c0
#define BOARD_NAME "RaspberryPi Pico"
uint8_t getUniqueSerialNumber(uint8_t* name);
void _ontouch1200bps_();
#define SPI_HOWMANY (1)
#define SPI_MISO (digitalPinToPinName(PIN_SPI_MISO))
#define SPI_MOSI (digitalPinToPinName(PIN_SPI_MOSI))
#define SPI_SCK (digitalPinToPinName(PIN_SPI_SCK))
//#define WIRE_HOWMANY (1)
#define WIRE_HOWMANY (2) //default 1
#define I2C_SDA (digitalPinToPinName(PIN_WIRE_SDA))
#define I2C_SCL (digitalPinToPinName(PIN_WIRE_SCL))
#define I2C_SDA1 (digitalPinToPinName(PIN_WIRE_SDA1))
#define I2C_SCL1 (digitalPinToPinName(PIN_WIRE_SCL1))
(以下、省略)
この例では下記の2組のピンペアを定義しています。
⓵ GP16(SDA,21ピン) / GP17(SCL,22ピン) ---- Wire
⓶ GP18(SDA,24ピン) / GP19(SCL,25ピン) ---- Wire1
■■■ 2系統I2Cの同時使用の例 ■■■
BME680で測定した温度・湿度・気圧・ガスの各センサの測定値を OLEDに表示します。
前記のpins_arduino.hで定義した通り接続します。
⓵ GP16(SDA,21ピン) / GP17(SCL,22ピン) ---- Wire ---- BME680
⓶ GP18(SDA,24ピン) / GP19(SCL,25ピン) ---- Wire1 --- OLED
スケッチは、BME680 ライブラリの中にあるものを使用します。
[ファイル]⇒[スケッチ例]⇒[Adafruit BME680 Library]⇒[bme680oled]をベースに下記のように変更しました。
⓵ Adafruit_BME680 bme(&Wire);
⓶ Adafruit_SSD1306 display(128, 64, &Wire1, -1);
⓷ setup()で内蔵LEDを点灯
⓸ loop()で内蔵LEDを100ms間点灯
//**************************************************************************
// Raspberry Pi Pico + Adafruit BME680+ OLED
// 2系統のI2Cを同時に使用する
// (1) 下記フォルダの pins_arduino.h を編集し、使用するピン番号等を定義する。
// C:\Users\user\Documents\ArduinoData\packages\arduino\hardware\mbed_rp2040\2.2.0\variants\RASPBERRY_PI_PICO\
// (2) このスケッチでは、Wire とWire1 として使用する。
//
// 2021/10/16 R.K
/***************************************************************************
This is a library for the BME680 gas, humidity, temperature & pressure sensor
Designed specifically to work with the Adafruit BME680 Breakout
----> http://www.adafruit.com/products/3660
These sensors use I2C or SPI to communicate, 2 or 4 pins are required
to interface.
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing products
from Adafruit!
Written by Limor Fried & Kevin Townsend for Adafruit Industries.
BSD license, all text above must be included in any redistribution
***************************************************************************/
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10
#define SEALEVELPRESSURE_HPA (1013.25)
//Adafruit_BME680 bme; // I2C
//Adafruit_BME680 bme(BME_CS); // hardware SPI
//Adafruit_BME680 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK);
Adafruit_BME680 bme(&Wire); // I2C
//Adafruit_SSD1306 display = Adafruit_SSD1306();
Adafruit_SSD1306 display(128, 64, &Wire1, -1);
void setup() {
digitalWrite(LED_BUILTIN, HIGH); // 内蔵LEDを点灯
Serial.begin(9600);
Serial.println(F("BME680 test"));
// by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (for the 128x32)
// init done
display.display();
delay(100);
display.clearDisplay();
display.display();
display.setTextSize(1);
display.setTextColor(WHITE);
if (!bme.begin()) {
Serial.println("Could not find a valid BME680 sensor, check wiring!");
while (1);
}
// Set up oversampling and filter initialization
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
}
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // 内蔵LEDを点灯
delay(100);
digitalWrite(LED_BUILTIN, LOW); // 内蔵LEDを消灯
display.setCursor(0,0);
display.clearDisplay();
if (! bme.performReading()) {
Serial.println("Failed to perform reading :(");
return;
}
Serial.print("Temperature = "); Serial.print(bme.temperature); Serial.println(" *C");
display.print("Temperature: "); display.print(bme.temperature); display.println(" *C");
Serial.print("Pressure = "); Serial.print(bme.pressure / 100.0); Serial.println(" hPa");
display.print("Pressure: "); display.print(bme.pressure / 100); display.println(" hPa");
Serial.print("Humidity = "); Serial.print(bme.humidity); Serial.println(" %");
display.print("Humidity: "); display.print(bme.humidity); display.println(" %");
Serial.print("Gas = "); Serial.print(bme.gas_resistance / 1000.0); Serial.println(" KOhms");
display.print("Gas: "); display.print(bme.gas_resistance / 1000.0); display.println(" KOhms");
Serial.println();
display.display();
delay(2000);
}
※(注1)最初に書いたように、元のファイルは内容を変えずに pins_arduino.h-original で保存、変更した方のファイルは pins_arduino.h-DUAL-I2C のように変更し、ファイル名の変更だけで切替えるようするとよいでしょう。
※(注2)このファイルは、Arduino IDE が起動している状態で変更すると変更内容がすぐに反映されません。
その場合は、一度Arduino IDE を終了させて再度起動するとうまくいきました。
※(注3) このやり方は、あくまでも個人的な「実験」であり互換性と保守性の点でお勧めできる方法ではないと考えます。他の人と共同開発するとか、ネットなどで公開する可能性がある場合はこのような「非標準」の「特殊な」改造をしたことを意識し、且つ忘れないような工夫が必要です。
例:
・pins_arduino.hを変更した旨をスケッチにコメントとして明記する。
・変更したpins_arduino.hでコンパイルが終わったら、すぐに元の内容のファイルに戻す。
その内、公式に改良されるかもしれない(?)ことを期待したいです。
[4] シリアルとSPIも2系統
シリアルとSPIも2系統あります。ピンアウト図にはシリアルが5組、SPIが5組のピンペアがあります。
pins_arduino.h には、I2C と同様に
#define SERIAL_HOWMANY 1
#define SPI_HOWMANY (1)
と定義されています。これらと関連する行を今回のI2Cと同様の編集をすれば同時に2系統使用できると思います。
実際に試してみるのは今後の課題です。
参考資料: ラズパイPicoのSPI/I2CのArduinoでの使用法に関するQ&A(英語)
この資料から色々なことが明らかになりました。メガネと髭のfacchinm という人は開発者のようですね。
英語ドキュメントも近年、精度が大きく改善されて実用レベルになったGoogle翻訳を使えば楽に読めます。
【関連記事】