4

4 マイコンプログラミング基礎 つづき

4.1 アナログ出力

アナログというのは「連続的な」という意味ですが、ここでは 0 (0V) と 1 (3.3V) の中間の電圧を出力するという意味です。マイコン、もしくは、 Raspberry Pi では、 0 (0V) か 1 (3.3V) のデジタル信号を扱うのが基本であるため、アナログ出力を扱うにはPWMという追加の知識が必要になります。これはデジタル出力を使って擬似的にアナログ出力を実現する方法だと思ってください。

PWMの理論

マイコンのデジタルピンからの出力は、原理的にはHIGH(例えば3.3V)かLOW(例えば0V)のどちらかだけです。しかし、0Vから3.3Vまでの間で電圧を自由に変えたいという場合があります。例えば、LEDの明るさを調整したい場合などです。こういう時には、 PWM (Pulse Width Modulation) という方法を用います。

PWMの仕組みを一言で説明すると、HIGHとLOWを素早く切り替えることによって、HIGHとLOWの間の電圧を擬似的に作り出す方法です。以下の図をみてください。

このようにHIGHとLOWを規則的に繰り返す信号を、パルス波と言います。上記の図で示しているパルス波は、ある一定時間で同じパターンを繰り返していますね。このような繰り返しの間隔を周期と呼び、1秒間に何周期あるかを周波数と呼びます。周期の始まりは、LOWからHIGHに変わる瞬間で、周期の終わりは、次にLOWからHIGHに変わる瞬間です。周波数1kHzと言ったら、1秒間に1000回の周期が起こるような波形を意味します。

図で示したパルス波は、1周期におけるHIGH(3.3V)とLOW(0V)の間隔が、ちょうど1/2ずつになっています。周波数が十分に高い場合,このデジタルピンにかかる電圧は、3.3V / 2 = 1.65Vがずっとかかっているのと同じ意味を持ちます。これがPWMです。実際に電圧を変えるのではなくて、擬似的に電圧を変えているのです。

PWMを使った場合にデジタルピンにかかる電圧は、デューティ比(duty ratio)によって決まります。デューティ比とは,1周期の間にHIGHになっている割合のことです。HIGHが3.3Vの場合、デューティ比50%ならば1.65V、10%ならば0.33Vとなります。

PWMを使用する場合、どのくらいの周波数が必要かは難しい問題なので、あまり深入りしないことにしますが,1Hzではだめなことは明らかです。1Hzということは、HIGHとLOWが同じ時間であるとすると1周期内で0.5秒ずつです。LEDで実験すると単に点滅するだけです。

PWMの周波数は、デジタルピンの用途によって変わるとされています。例えばLEDを使う際に必要な周波数,モータを使う際に必要な周波数という具合です。モータの場合には10kHzから20kH位が適当でしょう。LEDだと、100Hzくらいでも大丈夫だと思います。

例えば、10kHzのPWMをデューティ比20%で実現したいという場合には、1周期は100μsですから、20μsの間HIGHを出力して、80μsの間LOWを出力して、というのを繰り返す必要があります。これを行うにはプログラム上のタイマーで20μsや80μsを計測するということに原理的にはなります。

PWMの実際

以上がPWMを使ってアナログ出力を(擬似的に)実現するための理論です。しかし、実際にPWMを使うときはもっと簡単で、 GPIO Zero には、PWMのためのライブラリが用意されています。このライブラリを使えば、プログラムする際にタイマーを設定するなどの面倒なことは必要なくなります。

GPIO Zero に、PWMLEDとPWMOutputDeviceという2つのクラスがありますが、ここではPWMLEDの方を使います。この2つのクラスの詳細は、 GPIO Zero のリファレンスの"API – Output Devices"を見てください。

https://gpiozero.readthedocs.io/en/stable/api_output.html

下記のプログラムは、異なるデューティ比でLEDを交互に光らせるものです。デューティ比80%の時には明るく光り、デューティ比10%の時には暗く光るはずです。

from gpiozero import PWMLED

from time import sleep

 

led_pin = 17

 

led_pwm = PWMLED(led_pin)

 

def main_loop():

    while True:

        led_pwm.value = 0.8  # デューティ比80%

        sleep(0.5)

        led_pwm.value = 0.1  # デューティ比10%

        sleep(0.5)

 

if __name__ == '__main__':

    main_loop()

4.2 アナログ入力

デジタル入力は、端子に一定以上の電圧がかかっているか、かかっていないかという2状態を入力として識別することでした。アナログ入力は、端子にかかる電圧の変化を連続的な値として識別します。

例えば部屋の明るさは、太陽の位置等によって滑らかに変化していきます。光センサーは、その変化を電圧の変化に変換してマイコンに伝えます。

しかし、マイコンの中では、受け取った電圧の値をデジタルデータとして保持することになります。音楽データも、写真データも、動画データも、コンピュータの中では全てデジタルデータですよね。それと同じことです。つまり、アナログからデジタルへの変換が必要になるということです。マイコンを使用するときにそれを行ってくれるのが、 Analog to Digital Convertor (ADC) と呼ばれるデバイスです。一般的には、A/Dコンバータと呼ばれます。

MCP3002

しかし、 Raspberry Pi に搭載されているマイコンにはA/Dコンバータが内蔵されていません。そこで登場するのがMCP3002というICです。

今回配付するICの表面をよく読むと、MCP3002と書いてあると思います。MCP3002は10ビット2チャンネルのA/Dコンバータです。10ビットということは、2の10乗の解像度を持ちますので、1024段階の大きさの分類ができるということです。2チャンネルとは、2つの別々の入力を受け付けることができるということです。

2列合計8本の足がついていますが、この2列の足をブレッドボードの中央の溝をはさんで両側にくるようにブレッドボードに差し込むと、使い勝手がよいです。ちょうど下図のような具合です。

MCP3002のピンの役割は下図のようになっています。CH0, CH1と印字されているピンがアナログ入力に使用できます。これらのピンはセンサの回路に組み込んで使います。今回はCH0のピンだけを使います。それ以外のピンは、 Raspberry Pi のGPIOにそれぞれつなぎます。

MCP3002のVDD/VREFは Raspberry Pi の3.3Vに、
MCP3002VSSは Raspberry Pi のGNDに、
MCP3002のCLKは Raspberry Pi のSCLK(23番)に、
MCP3002のCS/SHDNは Raspberry Pi のCE0(24番)に、
MCP3002のDoutは Raspberry Pi のMISO(21番)に、
MCP3002のDinは Raspberry Pi のMOSI(19番)に、
それぞれつないでください。ブレッドボードをうまく使います。 Raspberry Pi の SCLK, CE0, MISO, MOSI のピンについては、GPIOピンの図を参考にしてください。

なんだかつなぎかたが複雑ですが、これはSPI通信という規格を使うときに毎度出てくるつなぎかたです。 SCLK, CE0, MISO, MOSI の4つの線でマイコンと外部デバイス(今回はA/Dコンバータ)の通信を行います。

MCP3002を使うためのライブラリ

以下の場所にライブラリの説明があります。MCP3002は GPIO Zero ライブラリの標準で使えるデバイスで、その名もMCP3002クラスというのがあります。

https://gpiozero.readthedocs.io/en/stable/api_spi.html#mcp3002

以下のサンプルプログラムを動かすと、MCP3002のCH0の電圧の値を読むことができます。読んだ値は、コンソールに出力しています。

from gpiozero import MCP3008

from time import sleep

 

sensor = MCP3002(0)

 

def main_loop():

    while True:

        value = sensor.value

        print(value)

        sleep(0.1)

 

if __name__ == '__main__':

    main_loop()

MCP3002(0)の引数の0がチャンネルの指定で、1と指定するとCH1のピンの電圧を読むことができます。SPI通信は、GPIOの特殊なピン(いつも固定のピン)を使うので、GPIOのピン番号を指定する必要はありません。

MCP3002のオブジェクト(ここでは変数sensorに代入されている)のvalueの値に、現在のピンの電圧に応じた値が入ります。プログラム中では、sensor.valueというようにすると、値が取得できるということです。値は、0Vから3.3Vのスケールに応じて、0.0から1.0の値で取得されます。実際の電圧値を知るには、もうひと計算必要だということです。

SPI通信を使うための準備

MCP3002は、 Raspberry Pi のマイコンとSPIという規格を使って通信します。 Raspberry Pi でSPIを利用可能とするには、1度だけ以下の設定を行う必要があります。

$ sudo raspi-config

光センサ

光を感知するセンサは、光センサ、あるいはフォトレジスタ(Photoresistor)と呼ばれます。

光センサには色々なタイプがありますが、ここではCdS(Cadmium Sulfide)セルを使った光センサを扱います。CdSセルとは、硫化カドミウムと呼ばれる物質を応用したセンサで、光の強弱によって抵抗値が変わります。

ここではCdSセルを使った光センサを単にCdSと訳して呼びます。CdSは以下に示すような見た目をしています。CdSは抵抗器の一種で、極性はありません。

CdSは以下に示す回路図のような使い方をします。抵抗の分圧の法則が成り立つ回路になっているのがわかりますでしょうか。

CdSの抵抗値は受ける光の強さによって変化しますから、もう1つ抵抗を使って可変抵抗の時と同じ状況を作ります。CdSは、強い光を受けるほどに抵抗値が小さくなっていきます。つまり、この回路ではCdSが受ける光が強くなると、アナログピンにかかる電圧が高くなります。CdSと抵抗の位置を逆にすると、光が強くなるほどアナログピンにかかる電圧は低くなります。

4.3 実習

実習1 タクトスイッチで調光する

タクトスイッチ1個とLED1個を使います。タクトスイッチを押すたびにLEDの明るさが5段階に変化するようにします。LEDの明るさの変更はPWMのデューティ比を変更させることで行います。

5段階の変化は、最も単純には「スイッチを押すたびに明るくなっていき、最も明るいときにスイッチを押すと最も暗い状態に戻る。」というようにすればOKですが、「明るくなっていく→暗くなっていく→明るくなっていく→暗くなっていく→...を繰り返す」など、条件にオリジナリティを出せるとよりよいです。

実習2 ストップウォッチを作る

ストップウォッチを作ってみましょう。使う部品は、タクトスイッチ1つとLED1つです。プログラムを起動すると待機中になります。このときLEDは消灯しています。タクトスイッチを押すと計測が開始されます。このときLEDは点灯します。計測中にタクトスイッチが押されると計測終了です。計測が終了すると、計測結果がターミナルの画面に出力されてLEDは消灯します。

時間計測は、pref_counter()を工夫して使うと可能です。 計測結果の出力は分と秒とミリ秒に分けて表示します。どのようにすれば、分と秒とミリ秒に分けられるのかを考えてみましょう。


started

stopped

2 18 527

ストップウォッチの状態は以下のように変化します。

この状態遷移図では、開始、待機中、計測中という3つの状態があります。矢印は状態を遷移させるイベントです。「スイッチが押される」というのがイベントで、「リセット」はその際に行われるアクションです。開始状態から待機中への矢印にはイベントが書かれていませんね。これは、無条件で即時に遷移することを意味します。つまりこれは、プログラムが起動すると即座に待機中になることを表しています。

状態を識別するためには、変数を用意する必要があります。例えば、is_activeという変数を用意して、is_active = Falseならば待機中、is_active = Trueならば計測中という具合に使います。

実習3 CdSを使って明るさを計測する

CdSで明るさを計測してみましょう。「光センサ」で説明されている回路を実際に作ってください。CdSを手で覆ったりして暗くしたり、スマートフォンの光を当てて明るくしたりして、どんな値が出力されるのか観察してください。