こんばんは、Reveの技術担当です。
今日はRaspberry PiとArduinoを、I2C通信で連携する方法をご紹介します。
自作ゲームコントローラーはいったんお休みです。
(今回の目標)
今回作るものは、表題の通り Arduino と Raspberry Pi をI2Cで通信するためのプログラムですが、決まったデータを送るだけではつまらないので、Arduinoのアナログ入力(analogRead)で得られた値をRaspberry Piに送るプログラムを作ります。
これを使用すれば、Arduinoで測定したセンサーの出力をRaspberry Piで扱うことができ、これを応用すれば、webサーバー上で温度データの監視といったシステムなども可能になります。
(I2C通信って何?)
I2C(Inter Integrated Circuit)とはフィリップス社で開発されたシリアル通信方式で、
データのやり取りを行う「SDA(シリアルデータ)」と、通信タイミングの動機を図る「SCL(シリアルクロック)」の2線をつなげるだけでデバイス間の通信が可能になります。実際は電源とグランド線を合わせて4本の線でつなぐことがほとんどです。
各線を並列につなげば、複数のデバイス間で通信することも可能です。
通信用の線が2本で済むため、速度より製造コストを抑えることが重要な場合に適しており、
比較的低速な電子機器や組み込みデバイスなどで使用されます。
I2C通信を行うためのライブラリとして、Arduino には「Wire」ライブラリ、Raspberry Pi には「i2c-tools」および「python-smbus」パッケージが用意されています。
Raspberry Piのプログラム制作にはPythonを使用します。
(Raspberry Piの設定)
ここでは Raspberry Pi にOSのインストールが完了した前提で進めます(Raspberry Pi B+を使用)。
1. I2C通信に必要なパッケージをインストール
ログイン画面で以下のコマンドを入力します。
$ sudo apt-get install i2c-tools python-smbus
2. Raspberry Pi のソフトウェア設定画面に入ります。
$ sudo raspi-config
3. 「8 Advanced Options」 -> 「A7 I2C」の順に選択します。
4. はい(YES)を選択すると、I2C通信が有効になります。
5. 再起動します。
$ sudo reboot
6. 再起動したらI2Cの設定を確認します。
以下のコマンドを入力し、特定のファイルを編集する画面に入ります。
$ sudo nano /etc/modules
7. もしファイル内に i2c-dev が書かれてなければ、ファイルの一番下に追記して保存します。追記した後は、もう一度Raspberry Piを再起動します。
これで、Raspberry Pi側の設定は完了です
なお、Arduino側は何の設定も必要ありません。
(プログラム)
では、それぞれのプログラムを見てみましょう。
プログラムは、それぞれコピーして貼り付けると簡単です(Raspberry Piの場合、テキストエディタで右クリックすると貼り付けられます)。
なお、Raspberry Piのソースは任意の場所で構いません。
[Arduino]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
; title="Arduino_i2c_Raspi1.ino"]#include <Wire.h> #define SLAVE_ADDRESS 0x04 //address of Arduino via i2c connection void setup() { //initialize i2c as slave Wire.begin(SLAVE_ADDRESS); //define callbacks for i2c communication Wire.onReceive(receiveData); Wire.onRequest(sendData); } void loop() { // wait 100ms delay(100); } //callback for received data void receiveData(int byteCount){ //Read all remaining data sent from Raspberry Pi while(Wire.available()){ int buf = Wire.read(); } } //callback for sent data void sendData(){ int val = 1023 - analogRead(1); //convert sensor output to 10-bit digital value byte adc[] = {val & 0xff, (val >> 8) & 0xff}; //separate the value to lower 8-bit and upper 8-bit. //Transfer ADC value to Raspberry Pi Wire.write(adc, sizeof(int)); } |
[Raspberry Pi(Python)]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
; title="i2c_raspi1.py"]#! /usr/bin/env /usr/bin/python import smbus import time #for RPI ver1, use "bus=smbus.SMBus(0)" bus=smbus.SMBus(1) #This is the address we setup in the Arduino Program address = 0x04 def writeNumber(value): bus.write_byte(address, value) #bus.write_byte_data(address, 0, value) return -1 def readNumber(): #get the sensor value from the Arduino(signed 16bit little-endian)... #number=bus.read_byte(address) number=bus.read_word_data(address, 0) return number def main(): while True: writeNumber(0) #sleep one second time.sleep(1) number=readNumber() print number #time.sleep(1) if __name__ == '__main__': main() |
(ソースコードの解説)
両方のソースを見たところで、それぞれの特に必要な部分を解説したいと思います。
[Arduino]
まず、必要なライブラリをインポートします。
1 |
#include <Wire.h> |
Arduinoのsetup関数で、I2C初期化とハンドラの定義を行います。
I2Cの初期化では、Arduinoはスレーブとして扱うため、Wire.beginメソッドによりアドレス(0x04)を設定します。
1 |
Wire.begin(0x04) // ソース内では SLAVE_ADDRESS が0x04として定義されている |
スレーブとして動かすため、マスタ(Raspberry Pi)からの指令に応じてデータを送ります。
そのため、マスタからのデータを受け取る関数(receiveData)と、マスタにデータを送る関数(sendData)を設定します。データ受信はWire.onReceiveメソッドで、データ送信はWire.onRequestメソッドで設定します。
1 2 |
Wire.onReceive(receiveData); //データ受信用の関数を設定 Wire.onRequest(sendData); //データ送信用の関数を設定 |
メインループ(loop)はdelay関数で100ms毎に処理を行うよう制限しています。
そして、肝心のデータ受信、送信用の関数の定義ですが、
まず受信用の関数は、int型の引数が1つ、戻り値なし(void)の形にします。
今回は、ただデータを受信するだけの関数を定義しています。
1 2 3 4 5 6 |
void receiveData(int byteCount){ //Read all remaining data sent from Raspberry Pi while(Wire.available()){ int buf = Wire.read(); } } |
続いて、送信用の関数は、引数、戻り値のどちらもなし(void)の形にします。
今回は、この関数内でanalogRead関数を呼び出してアナログ入力値を取得し、得られた10ビットの数値(int)を下位8ビットと上位8ビットに分けて送信する処理を行います。
送信時は、Wire.writeメソッドで下位8ビット、上位8ビットの順に送ります。
1 2 3 4 5 |
void sendData(){ int val = 1023 - analogRead(1); //アナログ入力(AD変換) byte adc[] = {val & 0xff, (val >> 8) & 0xff}; //10ビットのデータを、下位8ビットと上位8ビットに分ける Wire.write(adc, sizeof(int)); //2バイト分のデータを送る } |
ちなみに、sizeof関数は引数内が何バイトのデータかを判定するもので、今回はint型を直接指定します(intは、Arduinoでは2バイト)。
[Raspberry Pi]
今度はRaspberry Pi側のPythonプログラムを見ていきます。
まず、ライブラリのインポートをします。
1 |
import smbus |
そして、I2C通信を扱うためのインスタンスを定義します。
smbus.SMBusによって初期化できますが、RPIのバージョンが古い(ver1)の場合、引数を0に変更します(Raspberry A/Bの古いバージョン)。
1 2 |
#for RPI ver1, use "bus=smbus.SMBus(0)" bus=smbus.SMBus(1) |
また、Arduinoと同様にデータ受信用、送信用の関数をそれぞれ定義します。ただし、Arduinoと違い、必ずしも関数の定義は必要ではありません。
送信用の関数は引数で指定した数値を送る処理になっています。データの送信にはwrite_byteメソッドを使用します。
1 2 3 4 |
def writeNumber(value): bus.write_byte(address, value) #bus.write_byte_data(address, 0, value) return -1 |
受信用の関数は、スレーブ(Arduino)から送られたデータを受信する処理を実装します。データはint型のデータが2バイトに分かれて送信されるため、read_word_dataメソッドを用いて元の数値に戻しつつ受信します(wordは2バイトのデータ型)。
1 2 3 4 5 |
def readNumber(): #get the sensor value from the Arduino(signed 16bit little-endian)... #number=bus.read_byte(address) number=bus.read_word_data(address, 0) return number |
最後に、メイン関数(main)内でデータの送受信処理を実装していきます。
データ送信用の関数(writeNumber)でデータを送り、Arduinoからのデータを受信用の関数(readNumber)で受信して画面上に表示する処理です。
受信と送信の間に、time.sleepメソッドで1秒だけ処理を待機させます。
1 2 3 4 5 6 |
while True: writeNumber(0) #sleep one second time.sleep(1) number=readNumber() print number |
(プログラムの実行)
解説も終了したので、プログラムを実行してみましょう。
Arduinoにはプログラムを書き込んでおきます。
そのあと、ArduinoとRaspberry Pi(B+)のピンを以下のように接続します
Arduino ピン | Raspberry Pi ピン |
A4(SDA) | 3(GPIO2 SDA) |
A5(SCL) | 5(GPIO3 SCL) |
VCC(電源) | 電源ピン(5V: 2, 3.3V: 1) |
GND | 6(GND) |
(注意)
Raspberry Piは3.3Vで動作するため、電源が5VのArduino(Uno, Microなど)と接続すると、故障する可能性があります。
対策としては、3.3Vで駆動するArduino、あるいは電圧を変換する部品(レベルシフトIC)を使用します。電源が3.3VのArduino ProMiniを購入すればRaspberry Piと直接つないで大丈夫です(はんだ付けが必要)。
最後に、Raspberry Piで作成したプログラムの保存場所に移動し、以下のコマンドを実行してプログラムを起動します。
$ sudo python (ファイル名)
成功すると、以下のように数値が表示されます。
(ちなみに)
センサーなどの電圧出力をアナログ入力で測定して通信するのであれば、Arduinoではなく専用のAD変換ICを使用する方法もあります。Arduinoは汎用マイコンなので、データ受信時の処理や通信以外の出力を行うといった処理を加えてみるのも良いかもしれません。
実は、制作の関係でRaspberry Piを使いたかったのですが、Raspberry PiでAD変換できないのをハノイに来るまで知らなかったもので…(=_=;
何とか手段はないかと思い、苦し紛れ思い立った手段がこれでした。
需要があるかはわかりませんが、お役に立てれば(・ω・/
コメント