3

3 マイコンプログラミング基礎

3.1 Raspberry Pi へのWi-Fi接続を再開するには

前回大学のWi-Fiを介して Raspberry Pi への接続をしましたが、今回もう1度接続しようとしても接続できない可能性が高いです。(たまたまできてしまう可能性もなくはないです。)

これは、前回と今回とで、IPアドレスが変わっている可能性が高いからです。前回sshコマンドで入力したIPアドレスはもう有効ではなく、今回は別のIPアドレスになっているということです。そこで、まずはみなさんの Raspberry Pi のIPアドレスを調べなければなりません。これは毎回の実習で必要です。IPアドレスを調べる具体的な方法は、別途 Google Classroom に資料を用意しました。そちらを参照してください。

3.2 デジタル出力

GPIO

Raspberry Pi の表面を見ると、剣山のような細い針金が40本あります。これはGPIOと呼ばれるインターフェイスです。GPIOは General Perpose Input Output の略です。このGPIOにLEDやセンサなどをつなげることができます。

Raspberry Piの公式ページにGPIOについての説明があります。
https://www.raspberrypi.com/documentation/computers/os.html#gpio-and-the-40-pin-header
https://www.raspberrypi.com/documentation/computers/os.html#gpio-pinout

今後、このGPIOピンに接続して回路を作成していきますので、ピンのどこがどこであるかを調べられるようにしてください。pinoutコマンドで簡易的に調べることもできます。

$ pinout

LED

LEDは、光る部品です。日本語では発光ダイオード、英語では Light Emitting Diode といいます。ダイオードという電子部品の一種です。線が2本伸びていますが、どちらをプラスにして、どちらをマイナスにするか決まっています。これを、極性があるといいます。足の長い方の線をアノードと呼び、足の短い方の線をカソードと呼びます。アノードがプラスで、カソードがマイナスです。間違えると光りませんので、うまく光らない場合は、電流が流れる向きを確かめてみるとよいでしょう。

赤色LED、緑色LED、青色LEDがあります。ちなみに青色LEDは、3つのうちで一番最後に発明されました。発明者は日本人で、赤崎 勇・天野 浩・中村 修二の各氏です。この発明の対価をめぐって訴訟に発展したことは、みなさんも聞いたことがあるかもしれません。

また、LEDは流せる電流の上限値が決まっています。このような上限値のことを、絶対最大定格と言います。LEDに、絶対最大定格で決められている以上の電流を流すと、LEDは壊れます。

多くのLEDでは、20mAくらいの電流を流すと一番明るく光るようになっているようです。しかし、20mAを超えると急激に危険な領域に入ります。そこで、この講義では、5mAを流すことを標準とします。5mAでも十分に明るく光ります。

使用する抵抗の計算

3.3Vの電源を使ってLEDを光らせることを考えます。

それでは、抵抗を使う方法から説明しましょう。適切な抵抗を入れて、電流を制御するわけです。抵抗値を計算するにはオームの法則を使います。

V = I × R

これを変形させると、以下のような式を得ます。

R = V / I

今、電流 I は5mAと決めましたから、あとは電圧 V がわかればよいことになります。これは電源の電圧でよいのでしょうか?

それを知るには、LEDの性質を知らなければいけません。LEDは、電流が流れていれば光り、流れている電流が大きいほど明るく光る部品なのですが、ある一定以上の電圧がかかって初めて電流が流れ始めます。つまり、それまでは光らないということです。また、電流が流れ始めて光り出すと、ほんの少しの電圧の増加で大きく電流が増加します。これらのことから、LEDが光っている時には、光の強さによらず、ほとんど一定の電圧の低下が起こります。これを順方向電圧降下と呼びます。どのくらい電圧が降下するかはLEDによって異なり、正確にはデータシートを見なければわかりません。データシートには、順電圧(Vf)という名前で記載されています。ここではLEDの順方向電圧降下は2.0Vであるとして、その値を例にして説明をします。

LEDを光らせる時の抵抗値を計算するには、LEDにかかる電圧がほとんど一定であることを考慮に入れて、以下のように行います。

抵抗値 = (電源電圧 – 順方向電圧降下) / LEDに流したい電流

実際に3.3Vの電源電圧で計算してみましょう。電流の単位はAですから、5mAは0.005Aとなります。

抵抗値 = (3.3V – 2.0V) / 0.005A = 260Ω

回路に260Ωの抵抗を入れれば、5mA流れてくれるわけです。しかし、授業中に配布した部品の中には260Ωの抵抗は入っておらず、代わりに330Ωの抵抗が入っています。これでは、ダメなのでしょうか。330Ωでどのくらいの電流が流れるかを計算してみましょう。

電流 = (3.3V – 2.0V) / 330Ω = 約3.9mA

これでも全く問題ありません。330Ωの抵抗を使ったとしても3.9mA流れますので、まだ光ります。LEDを光らせるときには、5mA前後であれば、ある程度適当で良いです。多くの場合、抵抗の値は近ければなんとかなります。

それでは、10KΩにしたらどうでしょうか。

電流 = (3.3V – 2.0V) / 10000Ω = 0.13mA

0.13mAだと光らないか、もしくは光ったとしても本当にわずかです。

注意!: 抵抗をはさまずに、LEDだけを3.3Vとグラウンドの間に接続すると、一瞬でLEDはこわれます。今回使うLEDは順方向電圧降下が2.0Vであるので、3.3Vの電圧がかかると大電流が流れてしまうからです。LEDを使うときは抵抗をセットで使うと覚えておきましょう。

LEDを光らせる

それでは、実際にLEDを光らせてみましょう。電源の+側としては Rasperry Pi のGPIOに出ている3.3Vを使います。また、電源の-側としてはグラウンドを(GROUND)を使います。グラウンドは0Vを意味するきまりなので、覚えてください。

もう1度、GPIOピンの配置を確認して、3.3VがGPIOの何番ピンなのかを確認しましょう。1番ピンと17番ピンですね。今回は1番ピンを使います。グラウンドは6番、9番、14番、20番、25番、30番、34番、39番です。今回は6番ピンを使います。

ブレッドボードを使って、3.3Vから、330Ωの抵抗につながり、LEDに行き、固いジャンパワイヤ(青)を伝って黒いワイヤにたどり着くように回路を組みます。回路図どおりであることを確認しましょう。

注意!: 大事なことなのでもう1度書きます。抵抗をはさまずに、LEDだけを3.3Vとグラウンドの間に接続すると、一瞬でLEDはこわれます。LEDを使うときは必ず間に抵抗をはさんでください。

 Raspberry Pi を起動すると、GPIOピンにも電圧が出力されます。正しく回路を接続していると、写真のようにLEDは点灯するはずです。

プログラムから制御する

GPIOのピンうち、3.3V、5V、GROUNDは電源ですが、そのほかのピンには GPIO 0 のように、GPIOに続いて番号が記されています。これら GPIO 0 から GPIO 27 までは、入出力に使える汎用的なピンになります。

GPIOのピンをデジタル出力のために使うように設定にすると、そのピンの電圧を3.3Vにするか0Vにするかを決めることができます。このことを使ってLEDをプログラムから点灯させたり消灯させたりしてみましょう(点灯制御)。

回路図は以下のようになります。

上の回路図と比べると、電源の3.3Vの部分が「GPIO 17」と書かれた五角形に置き換わっています。この五角形はマイコンボードの入出力ピンを意味するきまりです。

ここで、GPIOのピンの電圧を3.3Vにするか0Vにするかを決めることができることを思い出してください。3.3Vにすればグラウンドとの間に電圧がかかるので電流が流れてLEDが点灯します。0Vにすればグラウンドとの間に電圧はかからないので電流は流れずLEDは消灯します。

実際にブレッドボードに回路を作った様子は以下のようになります。ややこしいですが、 GPIO 17 は11番ピンなので、11番ピンから抵抗にジャンパワイヤをのばします。電源の+側でも-側でもないワイヤは赤と黒以外(ここでは黄色)を使うことが慣例です。

PythonからGPIOを制御するためには、 GPIO Zero というライブラリを使います。以下のようにして、インストールしてください。(すでにインストールされている(そういうメッセージが出る)かもしれません。)これは当然 Raspberry Pi にログインして行います。

$ sudo apt install python3-gpiozero

適当な場所に、以下のプログラムを作成して保存します。保存したらシェルから実行してください。このプログラムは、1秒間隔でLEDを点けたり消したりします。いわゆる「Lチカ」プログラムというやつです。プログラムを終了するには、Ctrl-Cを押します。

from gpiozero import LED

from time import sleep

 

led = LED(17)

 

def main_loop():

    while True:

        led.off()

        sleep(1)

        led.on()

        sleep(1)

 

if __name__ == '__main__':

    main_loop()

3.3 デジタル入力

スイッチ

スイッチというのは、電線を連結したり切断したりするためのデバイスです。ボタンということもあります。スイッチには様々な種類が存在しますが、よく使うものはタクトスイッチと、トグルスイッチです。

タクトスイッチ(tactile switch)とは、ボタンの形状をしていて、押すと電線が連結され、離すと切断されます。写真のタクトスイッチは、足が4つ付いていますね。これらの足は、2つずつの2グループに分かれていて、同一のグループに属する足は、常に通電するようになっています。どの足とどの足が繋がっているかは、背面にマークがあることが多いです。写真の製品の場合には、背面に線が書いてあって、繋がっている足がわかります。マルチメータを使ってどの足とどの足が通電しているか確認しておきましょう。

タクトスイッチをブレッドボードに挿す時には以下のように真ん中の仕切りをまたぐようにするとよいでしょう。

トグルスイッチ(toggle switch)とは、電線の連結と切断を切り替えられるようにしてあるスイッチです。どちらかを選択すると、他を選択し直すまで同じ状態を保ちます。

スイッチの状態を感知するというのは、マイコンにとってはデジタル信号の入力を行うことです。ピンにかかる電圧は3.3Vか0Vのどちらかになり、3.3Vならば1、0Vならば0という数値で認識します。

スイッチは、直感的にわかりやすいデバイスですが、マイコンへの入力として使うときには少し注意が必要です。回路を誤ると、タクトスイッチが押されたかどうかをマイコンで検知できなかったり、スイッチを押したときに3.3Vとグランドがショートしてしまい、火花が飛んだり導線が焼き切れて焦げた匂いがしたりマイコンボードが破損したりして危険です。

正しくは以下のような回路を組みます。回路に使われる抵抗は、10kΩがよいでしょう。

タクトスイッチが押されると、抵抗の部分に3.3Vの全てがかかるはずです。よって、 GPIO 17 では3.3Vが計測されます。タクトスイッチが離されている時には、抵抗に電流は流れませんので両端で電圧差はなく、 GPIO 17 はGROUNDと同じ電圧になりますから0Vになるわけです。

このような回路をプルダウン回路と呼び、10KΩの抵抗をプルダウン抵抗と呼びます。

プルダウン回路とは逆に、タクトスイッチが離されている時に3.3Vが計測され、押されている時に0Vとなる回路も作れます。

これをプルアップ回路と呼び、10KΩの抵抗をプルアップ抵抗と呼びます。この場合、スイッチが離されている時に3.3Vが計測されます。

プログラムから読みとる

それでは実際に回路を作って試してみましょう。プルダウン回路にしてみます。以下のようなプログラムを作成して保存してください。

from gpiozero import Button

from time import sleep

 

button = Button(17, pull_up=None, active_state=True)

 

def main_loop():

    while True:

        if button.is_pressed:

            print('pressed')

        else:

            print('not pressed')

        sleep(0.1)

 

if __name__ == '__main__':

    main_loop()

実行すると、not pressedが連続して出力されます。タクトスイッチを押すとpressedと出力されるはずです。このプログラムは GPIO Zero のButtonクラスを使っています。ボタンからの入力を監視しているのは以下のオブジェクトです。

button = Button(17, pull_up=None, active_state=True)

17はGPIO 17の17です。pull_up=Noneは Raspberry Pi のGPIOに内蔵されているプルアップ・プルダウン抵抗を無効にするための指定です。button.is_pressedはbool型の変数で、ボタンが押されているときはTrueになり、押されていないときにはFalseになります。より詳細は、以下の GPIO Zero のドキュメントを参照してください。
https://gpiozero.readthedocs.io/en/stable/api_input.html#button

チャタリング

先ほどの回路にLEDを追加して、スイッチを押すたびにLEDが光るか消えるかを変えることを考えます。タクトスイッチの読み取りは GPIO 17 で行い、 GPIO 27 にLEDをつないだものです。プログラムは、以下のように作ったとしましょう。

from gpiozero import Button

from gpiozero import LED


button = Button(17, pull_up=None, active_state=True)

led = LED(27)

 

def main_loop():

    while True:

        if button.is_pressed:

            led.toggle()

 

if __name__ == '__main__':

    main_loop()

このプログラムは、タクトスイッチが押されるたびにLEDの状態が変わることを意図して作ったものです。led.toggle()というメソッドは、LEDの状態を反転させます。LEDが消灯している時に呼び出されると点灯し、点灯している時に呼び出されると消灯します。

しかし、このプログラムは思ったように動かないはずです。このプログラムは、タクトスイッチを押しっぱなしになっているときに、何度もled.toggle()が呼び出されてしまうからです。プログラムは高速に動作しているので、普通にボタンを押しただけでも何度もループしてLEDのオンオフが反転してしまいます。

LEDがコロコロ反転してしまうのは困るので、タクトスイッチが押された瞬間を検知して、そのときだけ反転させることにします。考えかたとしては、 GPIO 17 の値を読んだ時にそれがTrueで前回読んだ時にはFalseだった場合が、タクトスイッチが押された瞬間と考えられそうです。これをもとにプログラムで表現してみます。

from gpiozero import Button

from gpiozero import LED


button = Button(17, pull_up=None, active_state=True)

led = LED(27)

 

def main_loop():

    previous = False

    current = False

    while True:

        previous = current

        current = button.is_pressed

        if previous == False and current == True:

            led.toggle()

 

if __name__ == '__main__':

    main_loop()

このようにすると、previousには前回ボタンの状態を読んだときの値が、currentには今回ボタンの状態を読んだときの値が記憶されることがわかるでしょうか。if文では、前回がFalseで今回がTrueのときにLEDを反転させています。

しかし、実際に実験してみると、これも思ったようには動かない場合があります。タクトスイッチを押してもLEDの状態が変わらない場合です。これは、スイッチを使うときに起きるチャタリング(chattering)という現象が原因です。英語だとchatteringよりもbouncingと表現することが多いです。

スイッチは、金属板に金属板を押し付けることによって電線を連結します。この時、非常に短い時間で金属板同士が弾きあいます。結果として、以下の図で示すような現象が起こります。この図はプルダウン回路ではスイッチが押されたとき、プルアップ回路ではスイッチが離された時の様子です。

閾値(しきいち)は、この値以上あるいは以下でHIGHやLOWを認識するという境界です。この図で示すとおり、人間が1回だけスイッチを押したつもりでも、複数回のHIGHとLOWが起こります。この結果、人が意図しないスイッチのオンオフが起こってしまいます。そのために、先ほどのプログラムは、意図通りに動かないときがあるのです。

チャタリングはスイッチを押した時にも、離した時も起こります。スイッチによっても違いますが、授業で配布したタクトスイッチの場合、離した時に起こることの方が多いように思います。※まったく起こらない場合もあります。

チャタリングは、ハードウェアの工夫でも、ソフトウェアの工夫でも防ぐことができます。実際の工業製品の場合には、ハードとソフトの工夫を組み合わせることが多いと思います。この講義では、ソフトウェアの工夫のみを行います。ただし、ソフトウェアの工夫のみでチャタリングを完全に除去することはなかなか難しいです。

Pythonプログラムでタイマーを使う

チャタリングに対するソフトウェアの対策にも使えるのですが、Pythonプログラムでタイマーを使いたい場合は perf_counter() という関数呼び出しを使うことができます。 perf_counter() は、このプログラムが起動された時からの経過時間を秒で返します。秒よりも小さい単位は小数点以下の数字で表されます(ミリ秒やマイクロ秒)。

実際には、以下のライブラリの読み込みをプログラムの冒頭に追加する必要があります。

from time import perf_counter

3.4 実習

実習1 いろいろな抵抗の実際の値を測定する

手持ちの抵抗器のうち、値の違う3種類を用意して、マルチメータで実際に抵抗の値を測りましょう。手持ちの抵抗器がない人は、配りますので教員に聞いてください。

測った結果を実験レポートで報告してください。カラーコードの値とは少し違った値になることもあります。誤差があるということです。近い値(正確にはカラーコードの誤差の範囲)であればOKです。

実習2 いろいろな抵抗を使ってLEDを光らせる

GPIOの2番から5Vがとれます。これを使ってLEDを光らせましょう。この実習でも抵抗は実習1と同じ3種類を試してください。

それぞれの抵抗のときに、LEDに何mAの電流が流れているのかを計算してください。結果には、回路図(手書きでよい・ただし読みやすいように太字ペンや照明の調整を使いましょう)、電流の計算、光らせたときの写真を含めてください。

実習3 スイッチでLEDをオンオフ

タクトスイッチを押すたびに、LEDが点いたり消えたりオンオフが変化するようにします。動作させてみると、チャタリングが発生するかもしれません。よく観察して、発生しているのかしていないのかをレポートしてください。

発生した場合は、どのようにすれば解決できるのかも考えてみてください。チャタリングはスイッチのハードウェアに依存して発生するものですが、ソフトウェアによって対策をとることも可能です。もし実装することができたり、実装できなくても解決策を考えることができたら、レポートでアピールしてください。(発展問題)

実習4 スイッチで点滅パターンを最初から

まず、自分が好きだと感じる点滅パターンを自由に作成してください。単純な点滅の繰り返しだけでなくより複雑な光りかたを、「生き物みたいな感じ」「金属探知機みたいな感じ」「R2-D2みたいな感じ」など、何かテーマを決めて作成してください。細かい調整を少しするだけで、大きく心地よさが変化する場合もあるので、時間が許すならばこだわって作成してください。

次に、タクトスイッチが押されたら、LEDの点滅パターンは最初からやり直すというプログラムを作成してください。スイッチが押されたらすぐに最初からやり直すようにできるとよいです。(ヒント: delay() の呪縛から逃れよう / sleep() の呪縛から逃れよう)

実習5 光りかたを予測する

3.2節のサンプルプログラムを少し変更した以下のプログラムを実行するとどうなるでしょうか。光りかたの予想と結果をまとめたうえで、なぜそうなるのかを考察してください。

from gpiozero import LED

from time import sleep

 

led = LED(17)

 

def main_loop():

    while True:

        led.off()

        sleep(0.01)

        led.on()

        sleep(0.01)

 

if __name__ == '__main__':

    main_loop()

なぜそうなるのかについては、そうなるということの証拠がつかめる場合は証拠を示してレポートで論じ、証拠がつかめない場合は推測でよいので必ず考えたことを書いてください。周りの人と相談しあってもOKですが、証拠や推測は必ずそれぞれがつかんでください。