はじめに
ここでは,Arduino UNOを使って「PCA9685 PWM & Servoシールド」の通信確認をします.
目次へ戻るには ここ をクリック
※本ページは実験のテキストです.
PCA9685のI2Cアドレス
シールド基板に載っている「PCA9685」は,I2C通信で動かすICです.
メーカーの データシート はとても読みにくいので,
販売店のページを見てみます.
参考: Adafruit I2C接続16チャンネル12ビットPWM/サーボ シールド
デフォルトではI2Cアドレスは0x40 だそうです.
複数枚のシールド基板を重ねて使えるため,はんだ付けしてジャンパ(接続)することで,
I2Cアドレスを自由に変えることができるようになっているのですが,特に何もしなければ0x40だということです.
配線の接続
USBケーブルでArduino UNOとPCを接続してください.
※注意
ACアダプタはまだPCA9685シールド基板に差し込まないでください.
以前の受講生が書いたプログラムが残っているので,いきなり動き出します.
どんな動きをするか分かりません.
ロボットアームを動かす必要がある時までは,ACアダプタを挿す必要はありません.
スケッチを書き込む
では、本当にI2Cアドレス0x40でPCA9685が見つかるのかどうか探してみましょう。
メニューバーから[ファイル]-[新規ファイル]をクリックし、新しいプログラムを書く準備をしてください。
以下のプログラムを打ち込み、コンパイル/実行してみましょう。
ファイル名は i2c_detect.ino
としました。
#include <Wire.h> // I2C通信用のヘッダファイル
// 初期化関数
void setup() {
Wire.begin(); // I2C通信の開始
Serial.begin(9600); // シリアル通信の開始,通信速度は9600bpsで
Serial.println("I2C address checker"); // 開始メッセージ
}
// ループ関数
void loop() {
// 番地が読みやすいように表の補助を出力
Serial.println(" 0 1 2 3 4 5 6 7 8 9 A B C D E F");
for (byte n = 0; n <= 0x7F; n++) { // I2Cアドレスを0~0x7Fまで順次調べるループ
adrCheck(n); // デバイス有無を調べて結果を出力
}
Serial.println(); // 1行空けて見やすくする
delay(2000); // 2秒待って繰り返す
}
// I2Cアドレスチェック関数
void adrCheck(byte adr) {
byte dummy; // ダミー変数
// 行頭(0x00, 0x10, 0x20,,,)のadrのときは,10,20,,,と上位ビットを表示して表を読みやすくする
if ((adr & 0x0F) == 0) {
print_hex2chr(adr); // アドレスを出力
Serial.print(":");
}
Serial.print(" "); // 区切り用に空白を入れる
// 指定のアドレスで空の通信をしてみて,応答があるかどうか試す
if (adr < 8 || adr > 0x77) { // 予約アドレスの範囲はチェックしない
Serial.print("xx"); // 予約アドレスにはxxと表示
} else {
// 指定アドレスにデータを書き込めるかどうかでI2Cデバイスがあるかどうかを判定
Wire.beginTransmission(adr); // adr番地でI2C通信を開始
Wire.write(&dummy, 0); // 変数のアドレスが必要なのでダミー変数を使う.送信データサイズは0
// I2C通信の終了,書き込みが正常終了していたら0が返る
if (Wire.endTransmission() == 0) {
print_hex2chr(adr); // 書き込み成功したアドレスをprint
} else {
Serial.print("--"); // 書き込めなかったので,無効の意味の--をprint
}
}
// 行末(0x0F)まで行ったら,改行して見やすくする
if ( (adr & 0x0F) == 0x0F) {
Serial.println(); // println関数は引数なしで使うと,改行だけする
}
}
// 16進数2桁をprintする関数
void print_hex2chr(byte x) {
Serial.print((x >> 4), HEX); // 上位4ビットぶんprint
Serial.print((x & 0x0F), HEX); // 下位8ビットぶん
}
動作テスト
Arduinoに書き込み終わったら,シリアルモニタを開いて文字を送信し,動作を確認しましょう.
シールド基板がちゃんとArduino UNOに挿さっていれば,以下のように0x40
に反応があるはずです.
応答のなかった番地には,「応答なし」を意味する --
が書かれています.
また,0x70番地も反応することがあります.
これは,複数のPCA9685を制御して,LEDを一斉に消すなどの同時操作をするためのアドレスです.
プログラム解説
プログラムにはコメントを多めに書いておきましたが,少々複雑です.
今回は0x40がちゃんと動いていることが確認できれば十分なので.
ここは読み飛ばしてもかまいません.
興味のある人だけ解説を読んでみてください.
ヘッダファイルの読み込み
I2C通信の機能を使うために,Wire.h
を読み込んでいます.
#include <Wire.h> // I2C通信用のヘッダファイル
ここでincludeしていないと,以降のプログラムでWire.
で始まるクラスを使うことができないので,書き忘れないよう注意しましょう.
初期化関数
次は初期化の関数であるsetup()
です.
// 初期化関数
void setup() {
Wire.begin(); // I2C通信の開始
Serial.begin(9600); // シリアル通信の開始,通信速度は9600bpsで
Serial.println("I2C address checker"); // 開始メッセージ
}
I2C通信を開始するためのWire.begin()
,
シリアル通信を開始するためのSerial.begin()
,
マイコンがちゃんと動いていることを示すための初期メッセージとしてSerial.println()
,
が書かれているだけです.
ループ関数
次はループ関数です.
// ループ関数
void loop() {
// 番地が読みやすいように表の補助を出力
Serial.println(" 0 1 2 3 4 5 6 7 8 9 A B C D E F");
for (byte n = 0; n <= 0x7F; n++) { // I2Cアドレスを0~0x7Fまで順次調べるループ
adrCheck(n); // デバイス有無を調べて結果を出力
}
Serial.println(); // 1行空けて見やすくする
delay(2000); // 2秒待って繰り返す
}
最初にSerial.println()
で固定メッセージを表示したあとは,
nが0~0x7Fまでの128回forループを回しています.
forループ内ではadrCheck()
の関数で応答があるかどうかチェックしています.
(この関数はloop
関数の下に書かれています)
つまり,0番地から0x7F番地までのアドレスと試しに通信してみて応答が返ってくるかどうかチェックしているわけです.
今回は0x40が正解なので,それ以外のアドレスでは応答が無いはずですね.
0x7F番地まで調べ終わると,人間が読みやすいように2秒ほど待ってあげています.
I2Cアドレスチェック関数と16進数表示関数
最も重要なアドレスチェックの関数と,16進数をSerial.printする関数を解説します.
// I2Cアドレスチェック関数
void adrCheck(byte adr) {
byte dummy; // ダミー変数
// 行頭(0x00, 0x10, 0x20,,,)のadrのときは,10,20,,,と上位ビットを表示して表を読みやすくする
if ((adr & 0x0F) == 0) {
print_hex2chr(adr); // アドレスを出力
Serial.print(":");
}
Serial.print(" "); // 区切り用に空白を入れる
// 指定のアドレスで空の通信をしてみて,応答があるかどうか試す
if (adr < 8 || adr > 0x77) { // 予約アドレスの範囲はチェックしない
Serial.print("xx"); // 予約アドレスにはxxと表示
} else {
// 指定アドレスにデータを書き込めるかどうかでI2Cデバイスがあるかどうかを判定
Wire.beginTransmission(adr); // adr番地でI2C通信を開始
Wire.write(&dummy, 0); // 変数のアドレスが必要なのでダミー変数を使う.送信データサイズは0
// I2C通信の終了,書き込みが正常終了していたら0が返る
if (Wire.endTransmission() == 0) {
print_hex2chr(adr); // 書き込み成功したアドレスをprint
} else {
Serial.print("--"); // 書き込めなかったので,無効の意味の--をprint
}
}
// 行末(0x0F)まで行ったら,改行して見やすくする
if ( (adr & 0x0F) == 0x0F) {
Serial.println(); // println関数は引数なしで使うと,改行だけする
}
}
// 16進数2桁をprintする関数
void print_hex2chr(byte x) {
Serial.print((x >> 4), HEX); // 上位4ビットぶんprint
Serial.print((x & 0x0F), HEX); // 下位8ビットぶん
}
ループ関数ではn
という変数でfor文を回していまいしたが,
アドレスチェック関数ではそれをadr
という名前の変数として受け取っています.
すなわち,adr
には0~0x7Fの数値が入って呼び出されることになります.
まずは,Serial.printされたアドレスが読みやすくなるように,16回チェックしたら改行して行頭から表示を始めるように書いてあります.
00:
10:
20:
. . .
のように行頭に上位4ビットの値がわかりやすいような表示をしています.
0~7,0x78~0x7Fのアドレスは使ってはいけない番地なので,
もしもadrがその範囲の値だったときにはxx
とだけ表示してすぐ終わります.
それ以外のアドレスだったときは,I2C通信を行います.
ArduinoでのI2C通信の書き方は,
- Wire.beginTransmission()で引数のアドレスで通信を開始
- Wire.write()でデータを送信
- Wire.endTransmission()で通信終了
という手続きです.
日本語リファレンスへのリンクを張っておきましたので,クリックして見てください.
通信に成功するとWire.endTransmission()
は戻り値として0を返すので,
成功したアドレスだけをprint_hex2char
関数で数字を表示し,
失敗したアドレスには--
と書き出します.
16個表示したら,Serial.println()
とだけ書いて改行しています.
まとめ
今回は,I2Cで使える全てのアドレスに総当りで通信を仕掛けてみて,
応答のあったアドレスを表示するプログラムを試してみました.
今回はPCA9685との通信が目的でしたが,
このプログラム自体は,他のI2Cデバイスとの通信チェックにも使えるので便利です.
余談
シングルボードコンピュータ「Raspberry Pi」のI2Cアドレスをチェックするコマンド
i2cdetect -y 1
が便利なので,
Arduinoでも同じように表示したい,という目的で作られたのが今回のプログラムです.
おわりに
これでI2C通信によってシールド基板上のPCA9685の存在を確認することができました.
次はPCA9685のライブラリのインストール方法を解説します.
目次 へ戻って次の作業を行ってください。