Pythonロボティクスコース レッスン 34

テーマ.9-1 割込み機能を利用した並行処理

便利な割込み処理について学習しよう!

テーマ9を通して学ぶこと

コンピュータが複数のプログラムや1つのプログラム内で複数の命令の流れを並行して処理することを「並行処理」といいます。並行処理の技術があることで、例えばパソコンは、同時に複数のソフトウェアを立ち上げて実行することができます。実際に、レッスンでもWebブラウザとプログラムエディタ(Mu)の2つのソフトウェアを同時に立ち上げています。そこで、テーマ9では「割込み処理」や「マルチスレッド」などの並行処理を実現するための様々な技術について学習します。

このレッスンで学ぶこと

このレッスンでは、「割込み処理」について新たに学習します。割込みは、他の命令を処理している途中で、新たに緊急性の高い命令が与えられた場合、それを優先して処理する仕組みです。割込み処理を理解するためには、まずコンピュータの仕組みから知る必要があります。そのため、今回は少し説明が長くなりますが、そこから解説をしていきます。しっかりと順番に読み進めていき、最後の課題で割込み処理を応用したゲームの制作に取り組みましょう。

コンピュータの仕組み

割込み処理を理解するためには、Pythonの文法だけでなく、コンピュータの「ハードウェア」の知識も身に着けておかなければいけません。そこで、このチャプターでは、Studuino:bitのような小型のコンピュータに着目して、その仕組みを解説していきます。

3. 1 ロボットの制御に使われる小型のコンピュータ

家電製品やロボットを制御する目的で使われる小型のコンピュータは、「マイクロコンピュータ」と呼ばれています。(※または、省略されて「マイコン」とも呼ばれます。)様々なソフトウェアがインストールされ、多用途なパソコンとは違い、マイコンは用途が限られています。そのため、処理能力の高さよりも小型でかつ低消費電力であることが求められます。

また、1個のマイコンのチップには、コンピュータの中心となる計算処理や周辺装置(※)の制御処理を行う「CPU(Central Processing Unit/中央処理装置)」の他に、プログラムやデータを記録しておくための「メモリ」や、外部の機器からの電気的な入力を受けたり、反対に外部の機器への出力を行ったりするための「汎用入出力端子(GPIO:General Purpose Input/Output)」など、いくつかの装置(ハードウェア)が組み込まれています。

※ 周辺装置にはモーターやセンサーだけでなく、キーボードやモニター、ハードディスクドライブ(HDD)やSDカードなどが含まれます。

そして、下の図はStuduino:bitに搭載されている「ESP32」という名前のマイコンです。

このマイコンの中には次の部品が組み込まれています。

【 ESP32のハードウェア構成 】
部品名役割
CPUコンピュータの脳ともいえる演算処理装置
内部メモリ(記憶装置)データやプログラムを記憶するための装置
タイマ時間の計測や一定時間が経過したことを通知する機能をもった電子回路
ウォッチドッグタイマウォッチドッグとは「番犬」を表す言葉で、コンピュータが正常に動作しているかどうかを定期的に監視し、異常があればリセットをかけるための特別なタイマ
クロック発振回路クロックと呼ばれる信号を作成するための電子回路。クロックは一定の周波数で与えられる信号で、コンピュータはクロックをペースメーカーとしてタイミングを取りながら、あらゆる制御を行っています。
A-D変換器センサなどから得られるアナログ(Analog)な信号をコンピュータが処理できるデジタル(Digital)な信号へ変換する電子回路
D-A変換器コンピュータが出力するデジタル(Digital)な信号をモーターなどのアクチュエータを制御するためのアナログ(Analog)な信号へ変換する電子回路
汎用入出力端子(GPIO)センサなど外部の機器からの電気的な信号の入力やモーターなどのアクチュエータを制御するための信号の出力を行うための端子
通信用インターフェースUSBケーブルを介してパソコンなどと通信を行ったり、SDカードへのデータの読み書きを行ったりするための制御を可能にする装置

MicroPythonでは、これらの部品を直接制御するためのモジュールが提供されています。次のチャプターでは、その一例を見ていきましょう。

MicroPython独自の機能

Pythonの実行環境は、ある程度のデータ容量をもち、高速な処理が行えるパソコンなどの汎用的なコンピュータ上で動作することを前提として作成されています。一方で、みなさんがレッスンで使用しているのは、マイコンで動作することを前提としてPythonを小型化した「MicroPython」の実行環境です。MicroPythonは小型軽量化されただけでなく、前のチャプターで紹介したマイコンの部品を制御するための機能を提供しています。これらの機能は、主にmachineモジュールにまとめられています。

MicroPythonにはmachineモジュール以外にも、ネットワーク通信を行うnetworkモジュールやBluetooth通信を行うbluetoothモジュールなどが、オリジナルのPythonにはない固有の機能として提供されています。

4. 1 machineモジュールとは

machineモジュールには、上で紹介した部品の制御用に複数の関数とクラスが定義されています。すべての関数やクラスについての詳細な説明はここでは行いません。興味のある人は、以下のMicroPython公式ドキュメントで確認してください。

※ 公式ドキュメントの解説には専門用語が多数含まれています。理解するには一定以上のハードウェアやソフトウェアに関する知識が必要となります。これらをすべて理解していなくてもレッスンを進めていくことはできますので、難しいと感じる場合は飛ばしてください。

MicroPython公式ドキュメント(日本語版)

【 machineモジュールで定義されている関数の一例 】
関数名処理内容
reset()外付けのリセットボタンを押すのと同じように、マイコンの状態をリセットします。
disable_irq()割込みの要求(Interrupt Request)を受付けない設定にします。
enable_irq()割込みの要求を受け付ける設定にします。
freq()CPUのクロック周波数を返します。
※「割込み」については、次のチャプターで詳しく説明します。
※ 「disable」は無効にする、「enable」は有効にするという意味がそれぞれあります。
※ 英語で周波数のことを「frequency」といいます。
【 machineモジュールで定義されているクラスの一例 】
クラス名役割
Pin汎用入出力端子(Input/Output pin)の制御を行います。
ADCセンサーなどから得られたアナログな信号をコンピュータが処理できるデジタルな信号へ変換します。(Analog-to-digital converter)
RTC日時の経過を追いかけるリアルタイムクロック(Real Time Clock)機能を提供します。
Timerタイマを利用して、指定した周期(1秒ごとや10秒ごとなど)で特定の処理を実行する機能を提供します。
※ 英語で変換器のことを「converter」といいます。

このレッスンで学習する「割込み処理」では、この中からPinクラスとTimerクラスを使用します。では、次のチャプターで実際にそれぞれのクラスを使った割込み処理のプログラムを書いてみましょう。

割込み処理

ある処理の実行途中に、別の処理を受け付けることを「割込み処理」といいます。このとき元の処理は一時的に中断され、割込みのあった処理を終えたあとで、実行が再開されます。

5. 1 割込み処理はなぜ必要なのか?

例えば、飲食店などで食器を洗って乾燥させ、棚に片付けるまでの仕事を考えたとき、各工程に人やロボット、専用の機械などがついていれば、同時並行で作業を進めることができます。このように作業を分担することで、作業全体を早く終わらせることができます。

処理能力の高いコンピュータでは、複数の演算処理装置が搭載されているため、これと同じように作業を分担でき、複数の処理を同時並行で行うことができます。一方で、マイコンのように処理能力がそれほど高くないコンピュータではこれができません。つまり、上の例でいうと作業者が1人(または機械やロボットが1台)しかいないため、ひとつの作業を終えてから次の作業を行うことになり、全体で見るとそれだけ多くの時間が掛かかってしまいます。

では、もしこの例で食器を洗っている途中に、急ぎ皿を使う必要が出てきた場合はどのように対処すべきでしょうか。恐らく、食器を洗うのを一時中断して、先に乾燥させて片付けるという行動を取るでしょう。

マイコンの割込み処理は、まさにこれと同じことを行います。このように、コンピュータ上で複数の処理を並行して切り替えながら行うことを「マルチタスク」といいます。割込み処理は、マイコンがマルチタスクを行うための手段として広く用いられています。

※ 割込み処理はマイコンだけでなく、パソコンなど汎用的なコンピュータ上でも行われています。

5. 2 割り込み処理の種類

コンピュータにおける割込み処理は大きく分けると、「ソフトウェア割込み」と「ハードウェア割込み」に分かれます。

  • ソフトウェア割込み(内部割込み)
    CPUの内部に要因がある割込み処理。プログラムの実行中に発生するエラーによって、CPUからの命令として要求されるものなどがあります。
  • ハードウェア割込み(外部割込み)
    CPUの外部に要因がある割込み処理。例えば、キーボードが押下されたり、周辺機器(スキャナーやネットワーク機器、カメラなど)からのデータ入力があった場合に、割込み要求用に用意された入出力端子の電圧を変化させることで要求が行われます。

一般的に、「割込み」と言われるときは「ハードウェア割込み」を指すことが多いようです。ここからは、例としても分かりやすい、ハードウェア割込みを実践していきましょう。

ハードウェア割込みの実践

ここからは、「タイマ割込み」と「外部端子割込み」の2種類のハードウェアを使った割込み処理について、実際にプログラムを書きながら学習します。

6. 1 タイマ割込み

マイコンの「タイマ」には、時間経過を計測したり、一定の時間が経過したことを通知したりする機能があります。このうち、通知機能を利用して指定した時間が経過したときや、一定時間おきに割込みを要求することを「タイマ割込み」といいます。

MicroPythonでは、machineモジュール内のTimerクラスを使用してタイマ割込みを設定できます。例として、「1秒おきにブザー音(C4)の再生と停止を切り替える処理」をタイマ割込みを利用して書いてみましょう。

■ タイマ割込みのプログラムの作成

タイマ割込みは、Timerクラスのインスタンスを作成して、init()メソッドを使って設定します。このinit()メソッドには次の3つの引数を渡します。

Timer.init(*, mode=Timer.PERIODIC, period=-1, callback=None)
※「*」以降の引数は、キーワード引数として渡す必要があります。
引数名内容
modeperiodに指定した時間が経過したときに一回だけ割込みを行う場合はTimer.ONE_SHOTを指定し、periodの時間が経過するたびに割込みを行う場合はTimer.PERIODICを指定します。
preriod割込みを行うまでに経過を待つ時間(単位:ミリ秒)を指定します。
callback割込みで行う処理をまとめた関数の名前を指定します。
※英語で「期間」のことを「period」といいます。

では、順番にコードを書いていきましょう。まずは、先頭で使用するクラスとオブジェクトをインポートします。

次に、割込ませる処理を関数として定義します。今回割込ませる処理は「ブザー音(C4)の再生と停止を切り替える処理」です。割込みの関数も、グローバル変数を共有しているため、この関数が切り替えスイッチとして機能するように、グローバル変数に再生と停止の状態を記録するコードを書きましょう。

追加【4行目、6行目~13行目】
※ 切り替えスイッチのように、2つの状態を交互に切り替えるための機構を「toggle(トグル)」といいます。

ここで注意したいのは、割込みで呼び出す関数に引数が1つ必要という点です。この引数は、タイマオブジェクトがinit()メソッドから呼び出すときに、タイマオブジェクト自身を渡すために使います。

では、最後にinit()メソッドを実行して、タイマ割込みを設定しましょう。

【 サンプルコード 6-1-1 】
追加【16行目、18行目】

では、完成したプログラムを実行して動作を確認しましょう。

■ 割込み処理を行う上での注意点

割込みで行われる処理はできるだけ最小限の命令に留めておかなければいけません。もし割込みで多くの命令を実行することになると、他の処理の実行に大幅な遅れが出る恐れがあります。

例えば、上の【 サンプルコード 6-1-1 】では、関数toggle_buzzer()で行うことを最小限にしていたため、以下のようにLEDディスプレイに繰り返しメッセージを表示する処理を並行して行う場合でも、ほとんど遅れが気になりません。

【 サンプルコード 6-1-2 】
追加・変更【2行目、18行目、19行目】

このプログラムを実行すると、スクロールを特に妨げることなく、ブザー音の再生・停止が制御できていることが分かります。

しかし、もしtimeモジュールのsleep_ms()関数を使用して同じような命令を書いた場合、ブザー音が鳴っている間は、LEDディスプレイへのスクロール表示が止まってしまいます。実際に以下のサンプルコードを実行して、違いを確認してみましょう。

【 サンプルコード 6-1-3 】
追加・変更【1行目、6行目~10行目、14行目】

このような結果となってしまうのは、割込み処理の実行中はメインの処理(スクロール表示)が中断されており、timeモジュールのsleep_ms()関数で停止している時間だけメイン処理の実行が遅れてしまうためです。

そのため、割込み処理を行う場合は、timeモジュールのsleep_ms()関数のような遅延処理の使用を極力避けて、別の方法を選択するようにしてください。

6. 2 外部端子割込み

ほとんどのマイコンには、外部のセンサからの電気的な信号(電圧の変化)の入力やモーターなどのアクチュエーターを制御するための信号の出力を行うために、「汎用入出力端子/GPIO(General Purpose Input/Output)」が備わっています。

下の図は、Studuino:bitで使用しているマイコン(ESP32)の各汎用入出力端子の位置を示しています。この端子を通じて、LEDディスプレイの制御や、ロボット拡張ユニットにつないだセンサやモーターの制御を行っています。

Studuino:bitでは、各入出力端子を次の用途で利用しています。

【 ESP32の汎用入出力端子とStuduino:bitでの用途の比較表 】
ESP32の汎用入出力端子名Studuino:bitでの用途
GPIO1USBケーブルなどを介したシリアル通信用(TXD:送信出力)
GPIO2LEDディスプレイの電源のON/OFF制御用
GPIO3USBケーブルなどを介したシリアル通信用(RXD:受信入力)
GPIO4LEDディスプレイの点灯パターンの制御用
GPIO5モーションセンサ(加速度、ジャイロ、磁気)
GPIO6内部のフラッシュメモリとの通信(読み書き)で使用
GPIO7内部のフラッシュメモリとの通信(読み書き)で使用
GPIO8内部のフラッシュメモリとの通信(読み書き)で使用
GPIO9内部のフラッシュメモリとの通信(読み書き)で使用
GPIO10内部のフラッシュメモリとの通信(読み書き)で使用
GPIO11内部のフラッシュメモリとの通信(読み書き)で使用
GPIO12Wi-Fi、Bluetooth通信確認用LED(ボタンB下のLED)
GPIO13ロボット拡張ユニットの「P16」
GPIO14電源ランプ用LED(ボタンA下のLED)
GPIO15ボタンA
GPIO16PSRAMの読み書き用
GPIO17PSRAMの読み書き用
GPIO18ロボット拡張ユニットの「P13」
GPIO19ロボット拡張ユニットの「P14」
GPIO21I2C通信用(SDA)
GPIO22I2C通信用(SCL)
GPIO23ロボット拡張ユニットの「P15」
GPIO25ブザー
GPIO27ボタンB
GPIO32ロボット拡張ユニットの「P0」
GPIO33ロボット拡張ユニットの「P1」
GPIO36ロボット拡張ユニットの「P2」
GPIO39ロボット拡張ユニットの「P3」

外部端子割込み」は、これら汎用入出力端子で電圧の変化を信号として検出したときに行う割込み処理です。電圧の変化は、センサからの入力が行われたときや、LEDやブザー、モーターなどを制御するときに起こります。

例えば、GPIO15に接続されているボタンAや、GPIO27に接続されているボタンBを押したりはなしたりすると、汎用入出力端子では次のような電圧の変化が起きます。

では練習として、ボタンAが接続されているGPIO15の端子に電圧の変化が起きると、特定の処理を割込むプログラムを作成してみましょう。

■ 外部端子割込みを行うプログラムの作成

汎用入出力端子の制御に関する機能は、machineモジュールのPinクラスにまとめられています。このPinクラスには、端子の電圧変化によって行われる割込み処理を設定するためのirq()メソッドが用意されています。

irqは「Interrupt Request」を省略した表記で、「Interrupt」には割込みという意味があります。

irq()メソッドを呼び出すときは、次の2つの引数を指定する必要があります。

Pin.irq(handler=None, trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING))
引数名内容
handlertriggerに指定した電圧の変化が起きたときに、割込みを行う処理を関数名で指定します。
triggerどのような変化が起きたときに割込みを行うのかを指定します。

引数triggerに指定できる変化として、次の2つがPinクラスに定数として定義されています。

  • Pin.IRQ_FALLING
    電圧が下がる変化(立ち下がり)
  • Pin.IRQ_RISING
    電圧が上がる変化(立ち上がり)

また、2つの変化を両方とも引数triggerに指定したいときは「|」のビット演算子を使ってつなぎます。ビット演算については、テーマ10で詳しく説明します。

# 電圧が下がったときと、電圧が上がったときの両方で割込みを設定するとき
trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING)

では、コードを書いていきましょう。これから作成するプログラムは、「ボタンAを押すと割込みが入り、ブザー音(C4)の再生/停止が切り替わる」という動作を行います。

まずは先頭で必要なクラスとオブジェクトをインポートしましょう。

ブザー音の再生/停止を切り替える割込みの処理は、タイマ割込みの学習で書いた【 サンプルコード 6-1-1 】と同じ関数を使います。ただし、こちらは外部端子割込みで呼び出されるため、Timerクラスのオブジェクトではなく、呼び出し元のPinクラスのオブジェクトを引数として1つ受け取ります。以下のようにコードを追加しましょう。

追加【4行目~13行目】

次に、外部端子割込み処理を設定するコードを書いていきます。

まずは、ボタンAを接続している汎用入出力端子を指定して、Pinクラスのインスタンスを作成します。Pinクラスのコンストラクタ(__init__()メソッド)には、2つの引数を渡します。1つめが、使用する汎用入出力端子の番号で、2つめがその用途です。この用途は次の定数で指定します。

  • Pin.IN
    端子を入力用に設定します。ボタンやセンサを接続する場合はこちらを指定します。
  • pin.OUT
    端子を出力用に設定します。LEDやブザー、モーターなどを接続する場合はこちらを指定します。

ボタンAはマイコンの「15番」の汎用入出力端子に接続されていて、ボタンは入力を受ける部品であるため、次のようにコードを書きます。

追加【14行目】

そして、作成したオブジェクトからirq()メソッドを呼び出して、割込みを設定します。割込みを行うタイミングは、ボタンを押したとき、すなわち電圧が下がるPin.IRQ_FALLINGを指定します。

【 サンプルコード 6-2-1 】
追加【16行目】

このプログラムを実行して動作を確認しましょう。動作が確認できたら、今度は15行目で引数triggerPin.IRQ_RISINGに変えてもう一度実行してみましょう。ブザー音の再生/停止が切り替わるタイミングがボタンAをはなしたときに変わるはずです。

変更【16行目】

6. 3 割込み処理を利用しないときとの比較

ここまでで書いてきた割込み処理のサンプルコードと同じ動作は、割込み処理を使わなくても実現できます。既にみなさんはその方法を知っていて、これまで何度もコードを書いてきました。

例えば、ボタンAを押すたびにブザー音の再生・停止が切り替わるプログラムは次のように書くことができます。

このプログラムは、「ボタンが押される」や「センサの値が変化した」などのイベントが発生しているかどうかを定期的に監視する処理を行っています。このように特定のイベントが発生したときに、それに対応した処理を行う仕組みを「ポーリング」といい、割込み処理とよく比較して説明されます。

つまり、テーマ8までのレッスンで作成してきたセンサを使うプログラムの多くは、ポーリングを行っていたことになります。

■ 割込み処理と比較したポーリングの長所と短所

ポーリングの基本はwhile文を使ってループ処理を書くだけなので、プログラムの構造としては比較的簡単です。また、割込み処理はそもそもハードウェアが対応していなければ使えませんが、ポーリングはソフトウェアだけで実現でき、ハードウェアに依存しない点が長所といえます。

しかし、ポーリングの場合はある処理で待ち時間が出てしまうと、他のイベントの発生を確認することができません。そのため、下のプログラムではLEDディスプレイへのスクロール表示が終わるまで、ボタンAが押されたかどうかが確認できず、好きなタイミングでブザー音の再生/停止を切り替えることができません。

割込み処理とポーリングはどちらもマイコンのプログラム開発ではよく使われる手法です。それぞれの長所と短所を踏まえた上で使い分けるように心がけましょう。

課題:割込み処理を利用したゲームの制作

ここからは課題として、学習した2種類の割込み処理を応用し、LEDディスプレイを使用した簡単なゲームを製作してみましょう。

7. 1 課題で制作するゲームの紹介

まずは、この課題で制作するゲームのプレイ動画を見てみましょう。

このゲームでは、緑色のドットで表わされたキャタクターをボタンで操作します。キャラクターに向かって壁(赤色のドット)が迫ってきますので、それをタイミングよくボタンを押してジャンプして避けます。ジャンプのタイミングがずれてしまうと失敗となり、20回連続で避けることができればゲームクリアとなります。

■ ゲーム内の処理

このゲームのプログラムでは、以下の処理を割込みで行います。

処理の内容割込みの種類割込みのタイミング
壁の移動タイマ割込み200ミリ秒おき
キャラクターのジャンプ外部端子割込みボタンAの押下
キャラクターの着地タイマ割込みジャンプの500ミリ秒後に1回のみ

これらの割込み処理とは別で、「LEDディスプレイの表示の更新」や「キャラクターと壁との衝突の検出」などをメインのループ処理内で行います。

7. 2 プログラムの作成手順

では順番に、このゲームに必要な処理を書いていきましょう。

■ 壁の移動処理

最初、壁はLEDディスプレイの右外に位置しています。そこから、250ミリ秒が経過するたびに1マス分左に移動します。LEDディスプレイの左端に移動したあとは、再び右外に戻ります。

上のような動きをするため、y座標は固定でx座標のみが時間の経過によって変化します。では、この処理をmoving()関数として以下のようにまとめましょう。

この関数は、タイマ割込みで呼び出します。そこで、あらかじめ専用のTimerオブジェクトを用意しておきましょう。また、タイマ割込みで呼び出された関数は、呼び出し元のTimerオブジェクトを引数として受け取るため、moving()関数に引数を1つ追加します。

追加・変更【1行目、5行目、8行目】

そして、壁を避けた回数もこの関数内で数えます。ここでは、壁の位置を右外へ戻すときに回数を増やすようにしてみましょう。

追加・変更【3行目、10行目、16行目】

■ キャラクターのジャンプ処理/着地処理

次にキャラクターがジャンプする処理と、ジャンプ後に一定の時間が経つと着地する処理を書いていきます。

ここで注意したいのが、ジャンプの処理と着地の処理で割込み処理の種類がちがうということです。ジャンプの処理は、ボタンAが押されたときに外部端子割込みで要求します。一方で着地の処理は、ジャンプの処理が行われたあとから500ミリ秒が経過したときにタイマ割込み1回だけ要求します。そして、着地処理のタイマ割込みはジャンプの処理の中で設定する必要がある点にも注意してください。

では、順を追ってコードを書いていきましょう。
始めに、外部端子割込みに必要なPinクラスを新たにインポートし、ボタンAに割り当てられている汎用入出力端子の番号を指定してオブジェクトを作成します。

追加・変更【1行目、7行目】

続けて、キャラクターがジャンプする処理をjumping()関数にまとめます。このjumping()関数は、Pinオブジェクトの割込み要求で呼び出されると、引数として呼び出し元のPinオブジェクトを必ず受け取ります。そのため、引数を1つ宣言する必要があります。

追加・変更【5行目、21行目~24行目】

次に着地の処理をlanding()関数にまとめます。この関数はタイマ割込みで呼び出されるため、呼び出し元のTimerオブジェクトを引数として受け取る必要があります。

追加・変更【27行目~30行目】

これで、着地の処理としてlanding()関数が定義できたので、ジャンプの処理のjumping()関数の中で、500ミリ秒後にタイマ割込みを設定します。専用のTimerオブジェクトを新たに作成し、init()メソッドを実行しましょう。この割込みは1回だけ行うため、mode引数にはTimer.ONE_SHOTを指定します。

追加・変更【8行目、28行目】

これで、ジャンプと着地の処理が定義できましたが、このコードには1つだけ問題があります。それは、ジャンプ中にボタンを押すと、jumping()関数が再度実行され、タイマ割込みが再設定されてしまうという点です。そこで、ジャンプ中かどうかを判断するためのフラグを用意し、ジャンプ中は一連の処理を行わないようにするコードを追加します。

追加・変更【11行目、24行目、26・27行目、30行目、35行目、38行目】

■ 衝突の検出処理

ゲームの途中でキャラクターと壁が衝突した場合は失敗になります。この衝突の判定を行う処理をdetect_collision()関数としてまとめます。衝突の判定はいたってシンプルで、キャラクターと壁が次の位置にある場合のみ、衝突していると判断します。

コードを書くと以下のようになります。

追加・変更【39行目~43行目】

■ LEDディスプレイの表示の更新処理

ここまでで書いたコードで、壁とキャラクターの座標の変化と衝突の判定を行うことができました。次は、壁とキャラクターをその座標でLEDディスプレイに表示します。

壁とキャラクターの座標は時間の経過や、プレイヤーがボタンを押すことで刻々と変化します。そこで、LEDディスプレイの表示も一定時間おきに更新するようにします。ここで定義するupdate_display()関数は、その表示の更新を1回だけ行います。

追加・変更【2行目、14行目、50行目~62行目】

■ ゲームの終了処理

ゲームの終了時には、「設定した割込み処理の解除」と「結果の表示」を行います。

Timerオブジェクトは割込み設定の解除用に引数不要のdeinit()メソッドが用意されています。一方でPinオブジェクトには解除用のメソッドがないため、代わりにもう一度irp()メソッドを実行し、そのときにhandler引数にNoneオブジェクトを指定します。

そして、ゲームを成功したかどうかは、変数countの数値で判断できます。この回数が20回に達していれば成功で、達していなければ失敗です。この結果はLEDディスプレイにスクロール表示します。

以上の処理はquit_game()関数にまとめます。以下のようにコードを書きましょう。

追加・変更【3行目、62行目~73行目】

■ ゲームの状態をリセットする処理

ゲームを何度も繰り返し挑戦できるようにするには、前に挑戦したときの状態から元の状態へリセットする必要があります。そのための処理をinit_game()関数にまとめましょう。

追加・変更【74行目~80行目】

■ メインのループ処理

最後にメインのループ処理を定義します。この処理はstart_game()関数としてまとめ、「ゲームの状態のリセット」➡「ゲーム開始のカウントダウン」➡「割込み処理の設定」➡「LEDディスプレイの表示更新/衝突の検出のループ」➡「ゲームの終了」を順番に実行します。

追加・変更【84行目~101行目】

これで、プログラムの完成です。プログラムの実行後、ターミナルからstart_game()関数を呼び出して、遊んでみましょう。

>>> start_game()

もし、実行時に発生するエラーが改善できない場合は、以下のコードと見比べて誤りがないかを確認しましょう。

【 サンプルコード 7-2-1 】

おわりに

8. 1 このレッスンのまとめ

このレッスンでは、コンピュータがマルチタスクを行うための方法として、新たに「割込み処理」について学習しました。また、割込み処理にはいくつかの種類があることを知り、その中から「タイマ割込み」と「外部端子割込み」について、実際にコードを書きながら、仕組みとその使い方を確認しました。

レッスンの前半では、マイコンの仕組みを簡単に紹介しましたが、ものづくりにおいては、ソフトウェだけでなく、ハードウェアも同じくらい重要な技術であり、将来ソフトウェアエンジニアとして働きたいという人でもハードウェアの知識を身に着けたことが必ず役に立ちます。もう一度レッスンを読み返しながら、知らない用語は検索で調べるなどして、理解を深めていきましょう。

8. 2 次のレッスンについて

次回のレッスンでも引き続き、並行処理の技術について学習します。

TOP