2018年04月28日

続・MicroPythonにTimerクラスを追加する(RPiベアメタル)

timer-free.png

先日作成したTimerクラスですが、複数タイマーのサポートやメソッドの追加を行いました。

今回追加した機能を含めたTimerクラスの仕様はこんな感じです。

・machine.Timer(id) {System Timerのインスタンスを作成する。idは1 or 3}

・init(period=a, mode=b, callback=c) {タイマを初期化し割り込みを開始する。最初の割り込みは呼び出し時点からperiodマイクロ秒後。modeはPERIODIC(繰り返し), ONE_SHOT(一度だけ), FREE(後述)のいずれか。callbackはselfを引数とする関数。パラメータ省略時のデフォルト値はperiod=4294967295、mode=PERIODIC、callback=None。}

・deinit() {割り込みを停止する。}

・counter(v) {vを与えなければ、init後に起きた割り込みの回数を返す。vを与えた場合は回数をvに設定する。}

・callback(f) {コールバック関数をfに設定する。コールバックさせたくないときはfをNoneにする。}

・period(n) {nを与えなければ、現在のタイマ周期の値を返す。nを与えた場合は周期をnマイクロ秒に設定する。}

インスタンスの作成時、idは0〜3を指定できますが、実際に使用できるのは1と3で、0と2は使用できません。
以前も書いたように、System Timerの0番と2番はGPU側のシステム(VideoCore)で使われています。

0番と2番のタイマについても、割り込みを発生させること自体は可能です。
ただ、割り込みの周期を設定するコンペアレジスタはGPU側で設定されるので、CPU側から設定しても上書きされてしまいます。

実用的な意味はあまり無いですが、System Timer 0と2の挙動を観察できるように、タイマの動作モードとして「FREE」を用意しました。
このモードでは、コンペアレジスタの値はperiod()で読み出すことができますが、書き込むことはしません。
その代わり、IRQハンドラの中でコンペアレジスタの値をTimerオブジェクトのperiodに設定するようにしてみました。
つまり、例えばmachine.Timer(2).period()でSystem Timer 2のIRQ発生時点でのコンペアレジスタの値を観測できます。

以下の例は、System Timer 2の割り込みに応じてperiod()の値を観測します。

import machine
t2 = machine.Timer(2)
t2.init(mode=t2.FREE, callback=lambda t:print(t.period()))
t2.deinit()


動かしてみると、コンペアレジスタの値が勝手に更新されていくのが分かります。
間隔を調べても、100msecのときも1秒以上のときもあるという不規則さで、何に使われているのかは分かりませんが。
なお、コンペアレジスタの値はprint(obj)でも観測できます。(objはTimerのインスタンス)
ラベル:MicroPython
posted by boochow at 23:47| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

ラズパイゼロ向きなUSB→マイクロUSB変換アダプタ

microusb-adapter.jpg


Raspberry Pi ZeroシリーズはUSBポートがマイクロUSBになっています。
キーボードやマウスを直結することも可能ですが、USBプラグがマイクロUSBになっている機器はあまり多くありません。
このアダプタはUSB(Type-Aオス)をマイクロUSBに変換するアダプタで、Raspberry Pi ZeroにUSB入力機器を直結する場合に便利そうです。
取り付けるとこんな感じになります。

microusb-adapter2.jpg


Sparkfunの製品で、スイッチサイエンスで224円で販売中です。
とりあえず2つばかり買ってみました。

USBをMicro-Bへ変換するアダプタ - スイッチサイエンス

接続するものが決まっている場合は、USB(Type-Aメス)をマイクロUSBに変換する、いわゆるUSB OTGケーブルよりも使いやすいように思います。
posted by boochow at 18:38| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

koshian3.0をとりあえず買っておいた

koshian3.jpg

品切れのままになっていたkoshian2.0に代わって、koshian3.0が登場しました。
スイッチサイエンスさんから販売されています。
気付いたらまた品切れになっていた、ということにならないように、とりあえず1個買ってみました。

Koshian3.0 - スイッチサイエンス

価格は以前のまま、スペック的にはDACが搭載されました、ということで一見大きな違いは無さそうなのですが、実はチップベンダが代わっています。

以前のkoshianはBroadcomのチップ(後にCypressに事業譲渡)が使われていたのですが、koshian3.0はSilicon Labsのチップに変わっています。
BGM11S12F256というチップですが、Cortex-M4 @ 38.4MHz、256KB Flash、32KB RAMというスペックです。

BGM11S Bluetooth モジュールSiP SiP|Silicon Labs

以前のkoshian2.0はBCM20737S(CYW20737S)というチップで、Cortex-M3 @ 24MHz、320KB Flash、60KB RAMというスペックでしたから、RAMは減りましたがCPUはパワーアップしています。

ESP8266に比べると半分くらいのスペックですが、小さくて電池駆動も可能なので、konashi環境としてではなく、ベアメタル開発用としても、もっと注目されてもよいモジュールだと思います。
ちなみにmicro:bitに使われているnRF51822はCortex-M0 @ 16MHz、256kB Fash、16kB RAMというスペックです。

私も、以前はkoshianのファームウェアのままで使用しましたが、時間ができたらベアメタルで開発してみたいと思いました。
ラベル:Bluetooth
posted by boochow at 18:23| Comment(0) | 日記 | このブログの読者になる | 更新情報をチェックする

2018年04月23日

MicroPythonにTimerクラスを追加する(RPiベアメタル)

timerclass-sample.png

先日の実験でRaspberry Piのタイマ割込処理がだいたい分かったので、MicroPythonにこのタイマ割り込みを実装してみました。

MicroPythonにはTimerクラスがあり、タイマーを使ってコールバック関数を呼ぶことができます。
タイマー起動後は、コールバック関数は自動で呼ばれますので、マルチタスクのようなことができます。
例えば、一定周期でLEDを点滅させるという処理をしながら、シリアルポートでデータを読み書きできます。
また、一定時間後に一度だけ実行するONE_SHOTモードもあります。

以下は今回作成したTimerクラスを使った簡単なサンプルです。


System Timerには4つのタイマーがありますが、今回はとりあえずタイマー3だけを実装しています。
inc_ctr()がコールバックさせる関数で、引数tにはタイマーオブジェクトが入ります。
この関数はグローバル変数counterを1ずつ増やします。

タイマーをinitメソッドで初期化するとタイマーが動作を開始します。
今回の実装ではクロックを1MHzとしているので、periodの単位は1マイクロ秒です。
period=1000000だと、1,000,000マイクロ秒つまり1秒周期でinc_ctr()が繰り返し呼ばれます。

一方、直後のwhileループはcounterが3以上になるまでループします。
ループしている間も、inc_ctr()は1秒おきに呼ばれていますので、3秒後にこのループをから抜けます。
すると今度は、ワンショットモードでタイマーを起動していますので、1秒後に"done!"とプリントアウトします。


Timerクラスはハードウェアタイマに強く依存しているため、同時使用できるタイマーの個数などの詳細仕様はCPU毎に異なっています。
Raspberry Piでは、以前解説したように、使用できるタイマはARM Timerが一つと、System Timerが2つです。
今回は、System Timerを1つだけ使い、インスタンスも1つしか持てないTimerクラスを実装してみました。

MicroPythonへのクラスの追加については以前も書きましたので省略します。

今回のポイントは割り込みの処理です。
まず、MicroPythonのメイン関数に割り込みベクタの設定とIRQ有効化の処理を追加します。
そして、IRQの割り込みハンドラの中でTimerクラスに関する処理を行います。
割り込みハンドラのコードは以下のようにしました。


割り込みの原因がシステムタイマ3だった場合、timer_root.callbackに保存されているコールバック関数を実行します。
ただし、割り込みハンドラの中で実行するのではなく、この関数を実行するというタスクを実行待ちキューへ追加します。
この処理はmp_sched_schedule()で行います。

コールバック関数が実際に実行されるのは、MicroPythonのメインの実行ループの中で関数mp_handle_pending()が呼ばれたときです。
つまり、適切なタイミングでmp_handle_pending()を呼んでやる必要があります。

MicroPythonのREPLが動いているときは、シリアルポートの入力待ちをしている間、ずっとIdleループになっています。
入力待ちの間もコールバック関数が実行されるように、ループの中からもmp_handle_pending()を呼ぶように処理を追加しました。
ちなみに他のプラットフォームでのMicroPythonの実装を見ると、ループの中で行うべき諸々の処理をまとめてmpconfigport.hの中でMICROPY_EVENT_POLL_HOOK というマクロを定義しています。
そして、上記のシリアルポート入力や、delay_ms()など、様々なループからこのマクロを呼び出すようになっています。

mp_sched_schedule's scheduled callbacks don't run on empty REPL lines ・ Issue #3273 ・ micropython/micropython


このような実装なので、タイマ割り込みのタイミングはあまり正確ではありません。
ESP8266の実装に関して、D.P.Georgeは「100 usec以下の精度を求めるならC、アセンブラ、あるいはViperを使うべき」と書いています。
ちなみにViperとは、組み込み向けリアルタイムPythonだそうで、現在はZerynthiに改名されているようです。

esp8266: can't time pulses with good accuracy ・ Issue #2052 ・ micropython/micropython


なお、今回実装にあたってSTM32、ESP8266、ESP32のTimerクラスの実装も調べました。
Timerクラスはどうしてもハードウェアタイマに依存しますので、プラットフォームごとの仕様の違いがかなりありました。

STM32シリーズは制御用マイコンなので、ハードウェアタイマーの個数も機能も多く、定期実行やワンショットなどの他に、PWMにも使えます。(AVRと似ています。)
MicroPythonの実装も、ハードウェアタイマーのパラメータをいろいろ設定できますし、コールバック関数を単に待ち行列に追加するのでなく、割り込みハンドラの内部で実行できるようになっています。
ただ、割り込みハンドラ内でMicroPythonを実行することになりますから、コンテキストの保存には十分配慮する必要があると思われます。
実装を見ると、nlr(non-local return)関連の関数が使われています。
これはUNIX等におけるsetjmp/longjmpのプラットフォームに特化した実装のようです。

class Timer – control internal timers − MicroPython 1.9.3 documentation

対照的に、ESP8266はハードウェアタイマのスペックが比較的貧弱で、EspressifのSDKにも依存するためか、機能はかなり削られています。
また、バーチャルタイマーという概念が導入されています。
これはESP8266でリアルタイムOSを動作させ、リアルタイムOS上のタスクとしてタイマのカウントをソフトウェア的に行うもののようです。
周期は1msec単位で指定できますが、精度は粗く、周期があまり一定していないという評価がフォーラムでもいくつか出ていました。
仮に精度が1msecあったとしても、60Hzの信号でも周期が2~4%ずれることになりますので、用途によってはちょっと微妙な場合もありそうです。

class Timer – control hardware timers − MicroPython 1.9.3 documentation

ESP32はハードウェアタイマが4つありますが、周期の指定は同様に1msec単位のようです。

ESP32 MicroPython: Timer interrupts | techtutorialsx

また、delay_ms()を実行中にmp_handle_pending()が呼ばれるのは10msecに一度であり、これはESP32で使用しているFreeRTOSのタスクスイッチの単位が10msecであるためだそうです。
delay_ms()でなくforループの場合は、ループ一周ごとにpendingされているタスクを実行できるようです。

esp32: time.sleep_us() doesn't check for pending events ・ Issue #3493 ・ micropython/micropython

posted by boochow at 01:21| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2018年04月20日

Raspberry PiにおけるBASIC IRQペンディングレジスタの構成

Raspberry Piの割り込みでは、割り込みハンドラの中でペンディングレジスタを調べて適切な処理をする必要があります。
前回書いたように、ペンディングレジスタは各ビットが「未処理の割り込み要因」を表しています。
特に、BASICペンディングレジスタは主要な割り込み要因を集めてありますので、ここでその内容をまとめておきます。

CPUから見ると、割り込みというのは入力端子の一種です。リセットボタンがつながるリセット端子がその代表的なものです。
Raspberry Piでは32ビットのディングレジスタが3つありますが、各レジスタの1ビットが、割り込み信号線1本のHigh/Lowの状態を反映しています。

下の図は、左側が割り込み信号線で、それぞれの信号線が異なる割り込み要因を表しています。
マニュアル p110)

pending-regs.png


図は右側の3つのレジスタと左側の割り込み信号線の関係を表しています。
上から32本(IRQ32-63)はGPUからの割り込み信号で、その状態はIRQペンディングレジスタ2(図ではIRQ1となっていますが、マニュアルのこれ以降のページではIRQ2となっています)に反映されます。
この中の何本か(A few selected GPU IRQs)は、同時にBASICペンディングレジスタにも入力されていますので、どちらのレジスタからも状態を確認できます。

IRQ32-63のうち、BASICペンディングレジスタに入力されていない信号全てのORを取ったものが、BASICペンディングレジスタに入力されています。
これにより、BASICペンディングレジスタでは「IRQペンディングレジスタ2のどれかに割り込みがあったこと」を知ることができます。

次の32本(IRQ0-31)も同様に、Video Coreからの割り込み信号で、その状態はIRQペンディングレジスタ1に反映されます。
またこれらの信号全てのORを取ったものが、BASICペンディングレジスタに入力されています。


そしてBASICペンディングレジスタですが、マニュアルによると

「A few selected GPU IRQs」:10本(bit10-19)
IRQペンディングレジスタ2の全信号のOR(bit9)
IRQペンディングレジスタ1の全信号のOR(bit8)
ARM Timerなど8つの割り込み信号(bit0-7)

が入力信号となっています。

前回紹介したLinuxカーネルのソースコードを見ると、「A few selected GPU IRQs」の具体的な中身がわかります。
また、実際には10本ではなく、11本(bit10-20)がGPU IRQの信号となっているようです。
マニュアル(p114)の情報と、Linuxのカーネルソースコードの情報を合わせると、BASICペンディングレジスタの各ビットの意味は以下のようになっています(赤字はマニュアルには無い情報です)。

basic-pending-reg.png


I2CやSPIなど主要な入出力関係の割り込みは、このレジスタだけで把握できそうです。
なおbit 20の「ARASANSDIO」は、Arasan社製SDIOコントローラということで、マイクロSDカードで使用していないもう一つのSDIOインタフェース(GPIO22-27)からの割り込みです。
posted by boochow at 01:41| Comment(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする
人気記事