MicroPython的午睡(12) ラズパイPico、簡単!マルチコアでLチカを

Joseph Halfmoon

Raspberry Pi Picoは「お求めやすい」価格のマイコンにしては珍しいデュアルコアのMCUです。一味違うのがMicroPythonからでも2番目のコアを「お手軽に」使えてしまうこと。扱いやすいです。今回は、ミニマムのデュアルコア利用のサンプルとして、走っているコア毎に光る色を変える、マルチコアLチカを作ってみました。ラズパイPicoのMicroPythonの特徴の一つか?

※「MicroPython的午睡」投稿順 Indexはこちら

(今回使用したサンプルプログラムの全文は末尾に)

ラズパイPicoのMicroPythonではデュアルコアの利用可能

デュアルコアの低価格マイコンが無い分けでないのですが、多くは片方のコアは「デディケーティッド」な用途専用で、ユーザーアプリはもう片方しか使えない、ということが多いような気がします。Linuxなど走らせるSoCプロセッサが「シンメトリ」で、今どのコアでこのプログラムが実行されているのか「知りたくもない」状況とは大分異なる様相です。ところが、ラズパイPicoのRP2040の場合、搭載している2個のコア、対称的で、特に片方に特定の役割があるようには見えませぬ。ただ、ブート時には共に走り始めるものの、2個のコアが並走しつづけることはなく、2番目の方は直ぐに「お休み」になって待機状態になるようです。ユーザープログラムの開始時点では1番目のコアだけが走っている、と。

2番目のコアを起動して仕事を割り当てれば、並行した処理が可能です。そしてラズパイPico上のMicroPythonでは “_Thread” モジュールを使ってそれが簡単にできます。ただちょっと心配なのは GIL (Global interpreter lock)の存在です。ここにも書きましたが、普通のPythonの場合、GILの縛りがあるので、マルチスレッドにしてもスループットは上がらず、もっぱらIO待ちの時間などの有効活用程度にその効果が限られてしまいます。しかし、ラズパイPicoは違います。Raspberry Pi Pico Python SDKのP.15から1行引用させていただきましょう。

The GIL is not enabled so both core0 and core1 can run Python code concurrently, with care to use locks for shared data.

GILによって同時に走行できるインタプリタが1個に縛られている普通のPythonとは違い、ラズパイPico上のMicroPython実行では、各コアでの並行処理が可能だと。素晴らしい。

上記のP.15には、実際に複数コアを使うMicroPythonのサンプルプログラムも掲載されているのです。そこでは1個目のコア(core0)から2個目のコア(core1)に起動かけているのですが、本当に2個目動いているのか、どっちのコアがソフトのどこを動かしているのかサッパリです。それでこのプログラムを実行中のコアが分かるように改造してみました(末尾に掲載。)

実行中のコア番号を得る

プログラムを実行中のコア番号を知るためには、RP2040のデータシートの中のSIO(Single Cycle I/Oです。シリアルIOではありません。)という部分のCPUIDレジスタを読めればよいです。これもデータシートにしつこく書かれていますが、「SIOのCPUIDレジスタは、Armコア内蔵のCPUIDレジスタとは名前は同じですが異なるレジスタ」です。Armコア内蔵のCPUIDレジスタは搭載しているArmコアの種類などを示すものです。以前の投稿で読み出している例はここに。それに対して、RP2040のCPUIDレジスタはArmコアの外側にある、コアに密接に結合したSIOとよばれるハードウエアの中で動作中のコアを識別するためのレジスタです。

  • アドレス:0xd0000000番地
  • データ:0が読み出せればcore0、1ならcore1

MicroPythonのプログラムが2つのコアで並行実行されるように仕掛けた後、それぞれのプログラム部分で上記のレジスタを読み出せば、今自分がどちらのコアで実行されているのかが判明します。それを外に表示するためには「伝統のLチカ」採用(一番簡単。)

赤く光ればcore0、青く光ればcore1

であります。そのための外付け回路は以下に。

RPi Pico MULTCORE blink Schematic実際にこのプログラムを走らせると、先頭のアイキャッチ画像に掲げましたる通り、起動時側は core0で赤く、_Threadでスレッドとして起動下側は青く光ります。

一般のMicroPythonでは、1コアであっても複数Threadを起動できます(GILのためスループット向上には役に立たないですが、IO待ちの時間を有効活用して、複数のIO処理タスクを並行して走らせるような処理には活用できます。)しかし、このRaspberry Pi Pico上での実装はそうではないようです。もう一箇所引用させていただきます。

Only one thread can be started/running at any one time,

ここの部分とか、2コア間のSIO機能使った通信とか待ち合わせ、排他処理など確認しておいた方が良さそうです。それはまた後で。

MicroPython的午睡(11) ラズパイPico、Thonny IDEで動作確認 へ戻る

MicroPython的午睡(13) ラズパイPico、マルチコアの排他制御など へ進む

マルチコアで実行されていることがLチカで分かるサンプル
import time, machine, _thread

def task1(n, delay):
    led1RED  = machine.Pin(17, machine.Pin.OUT)
    led1BLUE = machine.Pin(16, machine.Pin.OUT)
    led1STAT = False
    for i in range(n):
        cpuid = machine.mem32[0xd0000000]
        if led1STAT:
            if cpuid == 0:
                led1RED.high()
            else:
                led1BLUE.high()
            led1STAT = False
        else:
            led1RED.low()
            led1BLUE.low()
            led1STAT = True
        time.sleep(delay)
    led1RED.low()
    led1BLUE.low()

def task0(n, delay):
    led0RED  = machine.Pin(14, machine.Pin.OUT)
    led0BLUE = machine.Pin(15, machine.Pin.OUT)
    led0STAT = False
    for i in range(n):
        cpuid = machine.mem32[0xd0000000]
        if led0STAT:
            if cpuid == 0:
                led0RED.high()
            else:
                led0BLUE.high()
            led0STAT = False
        else:
            led0RED.low()
            led0BLUE.low()
            led0STAT = True
        time.sleep(delay)
    led0RED.low()
    led0BLUE.low()

_thread.start_new_thread(task0, (100, 0.5))
task1(100, 0.5)