ラズパイの苦手なアナログ信号の処理をArduinoに任せよう
本記事は前回の記事「ラズパイとArduinoをI2Cで接続【基本編】 ラズパイからPythonのSMBusモジュールを使ってArduinoにデータを送信しよう」の続編です。
基本編ではラズパイとArduinoをI2Cバス(「バス」とは「通信経路」のことです)で接続して、ラズパイから送ったコマンドでArduinoにつなげたLEDを点灯/消灯させました。また、実験を通してI2Cや接続のポイントを中心に解説しました。
今回は、どういったケースで「ラズパイとArduinoの連携が役立つのか?」について、より実用的な実験を行います。
目次
- ラズパイとArduinoの連携で弱点をカバーする
- 準備
- 実験(1) ラズパイとArduinoを接続してLEDを調光する
- 3.1. Arduinoのスケッチを作成
- 3.2. ラズパイとArduinoをI2Cで接続
- 3.3. ArduinoにLEDを配線
- 3.4. Pythonを使ってラズパイからArduinoにデータを送信
- 実験(2) アナログ回路の電圧を読み取る
- 4.1. Arduinoのスケッチを作成
- 4.2. Arduinoに半固定抵抗器を接続
- 4.3. Pythonを使ってラズパイでArduinoのデータを受信
- まとめ
1. ラズパイとArduinoの連携で弱点をカバーする
前回の記事、「ラズパイとArduinoをI2Cで接続【基本編】 ラズパイからPythonのSMBusモジュールを使ってArduinoにデータを送信しよう」ではラズパイとArduinoの違いを説明しました。
本記事では、具体的にどういったケースが有効か考えます。
1.1. ラズパイとArduinoの弱点とは
ラズパイはパソコンと同等のユーザーインターフェース(GUI)を持ち複数のタスクを同時にこなす処理能力があります。
一方、Arduinoが実行できるのはあらかじめコンパイルされたプログラム(スケッチ)一本だけです。そのため、作成したプログラム自体が処理するタイミングを正確に把握できるので、産業機器のような緻密な制御が必要な用途にも使用できます。
両者を組み合わせれば、ラズパイでネットワークを介した複数のタスクを同時にこなしながら、Arduinoが電子回路を制御するというような使い方ができ、お互いの利点を最大限に発揮できます。
では、それぞれの弱点を考えてみます。
Arduinoの弱点はやはりユーザーインターフェースがない点です。生産設備の制御のように電源が投入されたらひたすら同じタイミングで処理をこなすケースでは問題ありませんが、人間が操作する機器ではユーザーが入力した値で処理を変え、また処理結果をユーザーに通知することが必要です。ユーザーインターフェースがないArduinoでは簡単にできません。
一方、ラズパイはマイコンボードとして機器の中に組み込んだ際に周辺の電子部品との相性はあまりよくなく、特にアナログ回路との組み合わせは苦手です。
電子回路は「デジタル回路」「アナログ回路」に大別できます。
デジタル回路は信号を0と1の組み合わせで伝達する回路です。通常プログラム内の0は電圧が0V、1は電源電圧(ラズパイは3.3V、Arduinoは5Vです)に置き換えて電子部品同士で情報を交換します。電圧が0Vのことを「LOW」、電源電圧のことを「HIGH」と呼ぶ場合もあります。
そのため、ラズパイも「LEDの点灯/消灯」や「ボタンがON/OFFされた」などの単純な電圧がHIGH/LOWで制御できる場合には特に問題ありません。
一方、アナログ回路は電圧の微妙な変化を取り扱う電子回路です。単純に電圧が0Vの時、もしくは電源電圧の時に意味があるデジタル回路とは全く異なり、アナログ回路は0Vから電源電圧間の全てが意味ある信号です。
「LEDがだんだん明るくなるといった調光」「DCモーターの回転速度の調整」「可変抵抗器のツマミがどの程度回転したか」などは全てアナログ的な信号です。
これらの理由から「Arduinoはユーザーインターフェースをラズパイに任せる」「ラズパイはアナログ信号の処理をArduinoに任せる」のような両者の欠点を補う連携動作が最適です。
本記事では、この例として「ラズパイのコマンドでArduinoに接続したLEDをPWMで調光」と「ラズパイからArduinoに接続したアナログ回路の電圧を読み取る」2つの実験を行います。
1.2. ラズパイのコマンドでArduinoに接続したLEDをPWMで調光
PWM(Pulse Width Modulation)は、デジタル入出力ピンから出力される電圧(Arduinoは5V、ラズパイは3.3V)を高速にON/OFFすることで、0Vから電源電圧までの間の電圧を擬似的に表現する機能です。
あくまでも擬似的なアナログ信号なのでLEDの調光やDCモーターの速度調整などの用途に限定されますが、ほとんどの用途で十分対応できます。
ただしラズパイやArduinoから出力されるPWM信号は、あまり大きな電流を制御できません。モーターの速度調整にはトランジスタなどの増幅回路が必要です。
PWMの機能はラズパイにもあります。ところがラズパイのPWM(ハードウェアPWM)は2本しかありません。さらに2本の内1本はヘッドフォンの出力端子と共用です。ラズパイから音声を再生する場合、残るPWM出力は1本です。
PWMは、プログラム上でソフトウェアPWMを作成することもできます。これはPWMを動作させるためのプログラムから「一定の間隔で特定の入出力ピンをON/OFFする」ことによって疑似的にPWMを実現する方法です。ところがラズパイのように複数のタスクを同時に実行するシステムでは、ほかのタスクの処理も行われるため「一定の間隔」に微妙な差が出ます。例えばロボットなどの緻密な制御が必要な用途ではこの誤差が問題となる場合があります。
安定したPWM動作を行わせる場合、プログラムでPWMを動作させるより、ハードウェアPWMで動作させた方が確実です。
Arduino Unoには6本のハードウェアPWMがあります。さらに多くのハードウェアPWMが必要であれば、15本のハードウェアPWMを持つArduino Megaもあります。
また、I2Cバスは1個の親機に対して複数の子機を接続できるのでPWM信号が必要な数だけArduinoをI2Cバスに接続することもできます。
そこで、今回の実験ではPWM機能に乏しいラズパイからI2Cを通してArduinoに接続し、PWMを制御させてLEDを調光してみます。
1.3. ラズパイからArduinoに接続したアナログ回路の電圧を読み取る
ラズパイは、アナログ信号をプログラム内で取り扱えるようにするA/D変換の機能を持っていません。ラズパイでアナログ信号を読み取る場合は、別途A/D変換を行うICなどを取り付ける必要があります。
そこで、Arduino Unoが搭載する6本のA/D変換を活用してみます。ただしそのうちの2本はI2Cの通信で使用するので、全てのA/D変換モジュールを使うことはできません。
ArduinoのA/D変換は10ビットの分解能を持っています。これにより入力ピンにかかる0〜5Vの電圧を、スケッチでは0〜1023の数値(1024段階)として扱うことができます。
Arduinoに半固定抵抗器を取り付け、その回転量を電圧の変化として読み取り、結果をラズパイ上で表示します。
ArduinoのA/D変換を使ったことがない場合はチュートリアルが参考になります。
- AnalogReadSerial(英語)
- ReadAnalogVoltage(英語)
Arduinoでアナログ回路の電圧を読み取る方法が詳細に紹介されています。本記事で行う実験も、このチュートリアルをベースにI2Cバスの通信を加えました。
2. 準備
以下のものを準備してください。
ラズパイ本体
ブレッドボード
GPIO拡張基板
スイッチサイエンス製(互換可)
Arduino Uno
LED
抵抗
680Ω(LEDの破損防止用)
半固定抵抗器
手に入りやすいもの
GPIO拡張基板はAdafruit製(海外)やスイッチサイエンス製(国内)が入手しやすそうです。
ほかにも、さまざまな製品が販売されているので、皆さんの使いやすいものを探してみるのも良いでしょう。
上記に加えてブレッドボード用のジャンパワイヤが必要です。
ラズパイで作業するので、ディスプレイ、USBキーボードとマウスも必要です。リモート接続ができる場合は不要です。
3. 実験(1) ラズパイとArduinoを接続してLEDを調光する
ラズパイとArduinoをI2Cで接続してラズパイのPWMを拡張します。
この実験では、ラズパイの指示によってArduinoから出力されたPWM信号でLEDを調光します。
3.1. Arduinoのスケッチを作成
今回もArduinoのスケッチを先に作成して転送しておきます。
以下がスケッチの全体です。スケッチのあとに解説が続きます。
#include <Wire.h> const int led = 9; const int addr = 0x8; void setup() { // Joins the I2C bus as a slave at address 0x8 Wire.begin(addr); // Sets pin 9 as an output pin pinMode(led, OUTPUT); // Calls the changeBrightness function when the master sends a value Wire.onReceive(changeBrightness); } // Reads and sets the PWM value from the I2C bus void changeBrightness(int bitstream) { int brightness = Wire.read(); analogWrite(led, brightness); } void loop() { }
setup()関数内は前回の記事、「ラズパイとArduinoをI2Cで接続【基本編】 ラズパイからPythonのSMBusモジュールを使ってArduinoにデータを送信しよう」で使用したスケッチと大きな違いはありません。
I2Cバスから送られるデータを受信した際に呼び出す関数の名前がchangeBrightnessに変わっただけです。
changeBrightness()関数では受信したデータをそのままPWM信号の設定値として使用します。
ArduinoでPWMを出力するにはanalogWrite()関数を呼び出すだけです。
この関数は2つの引数をとり、1つめはPWM信号を出力するピン番号、2つめは0〜255までの数値です。
例えば127を指定すれば出力は50%となり、LEDの調光では中間程度の明るさになります。
analogWrite()関数を使用するために別途ライブラリーは必要ありません。ただし全てのデジタル入出力ピンからPWM信号が出力できるわけではありません。Arduino Unoでは3、5、6、9、10、11番ピンの6本です。
スケッチが完成したらArduinoに転送します。その後、I2Cバスをラズパイに接続します。
3.2. ラズパイとArduinoをI2Cで接続
I2Cバスの配線方法は以前の記事、「ラズパイとArduinoをI2Cで接続【基本編】 ラズパイからPythonのSMBusモジュールを使ってArduinoにデータを送信しよう」を参照してください。
3.3. ArduinoにLEDを配線
次にLEDを接続します。
以下の図のように電流が「Arduinoの9番ピン === LED === 抵抗(680Ω) === ブレッドボードの電源ライン(-)」と流れるように接続します。
LEDには極性があるので注意してください。LEDのアノード(長い方の端子)をArduinoに接続します。
3.4. Pythonを使ってラズパイからArduinoにデータを送信
続いてラズパイ側からArduinoにコマンド(データ)を送信します。
これも記事、「ラズパイとArduinoをI2Cで接続【基本編】 ラズパイからPythonのSMBusモジュールを使ってArduinoにデータを送信しよう」と大きな違いはありません。
前回の記事とはArduinoに送るコマンドが異なります。
ラズパイのターミナルからPythonを起動します。以下のコマンドを入力してください。
python3
I2Cを制御するライブラリーSMBusのインポートと、データの転送に使用する値の定義です。
from smbus import SMBus arduino = 0x8 i2cbus = SMBus(1)
それではwrite_byte()関数を使用して、Arduinoにコマンド(データ)を送信します。
i2cbus.write_byte(arduino, 180) i2cbus.write_byte(arduino, 35) i2cbus.write_byte(arduino, 250) i2cbus.write_byte(arduino, 120)
前回の記事はArduinoに接続したLEDのON/OFFですが、今回はLEDの明るさが変化したはずです。
4. 実験(2) アナログ回路の電圧を読み取る
ラズパイとArduinoをI2Cで接続して、ラズパイでアナログ信号を読み込む実験です。
Arduinoで読み取ったアナログ信号(半固定抵抗器の値)をラズパイで出力します。
4.1. Arduinoのスケッチを作成
Arduinoのスケッチを先に作成して転送しておきます。
以下がスケッチの全体です。スケッチのあとに解説が続きます。
#include <Wire.h> void setup() { // Join I2C bus as slave with address 8 Wire.begin(0x8); // Call the sendReading function when data is requested Wire.onRequest(sendReading); } // Reads the value from pin A0 and then writes it to the I2C bus void sendReading() { int reading = analogRead(A0); Wire.write(reading/4); } void loop() { delay(100); }
ArduinoをI2Cバスの子機として初期化する部分までは実験(1)と同様です。
今回は親機(ラズパイ)の要求に応じてデータを送信するためWire.onReceive()関数の代わりにWire.onRequest()関数を使用します。引数は実際にデータを送信する関数sendReadingです。
sendReading()関数の中ではanalogWrite()関数の代わりにanalogRead()を使ってA0ピンの値を読み込みます。読み込んだ値はピンの電圧に応じた0から1023までの整数値です。
次に読み込んだ値をI2Cバスへ送るためWire.write()関数を呼び出します。引数は送信するデータです。
送信するデータを4で割るのは、今回I2Cバス上で送受信するデータが1バイトだからです。前述の通りanalogRead()関数が返す値はピンの電圧に応じた「0から1023までの値」であり、この情報を扱うためには2バイト必要です。そこで4で割り1バイトの範囲である「0から255までの値」に変換して送信します。
loop()関数は実験(1)と同様、特別な意味はありません。
スケッチが完成したらArduinoに転送します。
4.2. Arduinoに半固定抵抗器を接続
最初にブレッドボード上の配線をI2Cバス・電源・グランドの状態に戻します。
ブレッドボードに半固定抵抗器を配置します。
半固定抵抗器の中央の端子とArduinoのA0ピンを接続します。
半固定抵抗器の残る2本の端子はそれぞれ5Vピン、GNDピンに接続します。
4.3. Pythonを使ってラズパイでArduinoのデータを受信
これでArduinoに接続した半固定抵抗器の値を読み取る準備ができました。
ラズパイのターミナルはPythonが起動した状態のはずです。I2Cを制御するライブラリーSMBusや、データの受信に使用する値などはそのまま使用できます。
以下のコマンドを入力してください。
今回はI2Cバスに送られるデータの読み込みなのでread_byte()関数を使用します。引数はI2Cバスのアドレスです。
成功するとArduinoが読み取った半固定抵抗器の値が返されます。ただし1バイトに収まるように4で割ったため0から255の値です。
i2cbus.read_byte(arduino)
Arduinoに接続した半固定抵抗器のツマミを回して(抵抗値を変化させて)から、もう一度上記のコマンドを入力します。
値が変化していれば成功です。
5. まとめ
ラズパイとArduinoをI2Cで接続すれば、それぞれのシステムの弱点をカバーしあうことができます。
特にラズパイは電子機器に組み込んだときのアナログ回路的な制御はArduinoほど得意ではありません。そこで、PWM信号とアナログ信号の読み取りの実験を通してラズパイの機能をArduinoで補完しています。
「ラズパイを使いたいがアナログ信号を扱えない」もしくは「Arduinoを使いたいがユーザーインターフェースが欲しい」など、どちらかだけでは実現が難しいような電子工作を考えている場合、両者をI2Cでつなげてしまうというのは実用的なテクニックです。
ぜひ皆さんも試してみてください。