GoにいればGoに従え(39) ラズパイPicoでもレジスタ直接アクセス。最初はCPUID

Joseph Halfmoon

TinyGo言語は、ハード依存性のあるMachineパッケージを使っていてもピン名を変更するくらいで別なマイコンへ移植可能なことが多くお楽。しかしディープに機種依存なハードを使うためにはハードウエアを直接操作しないとなりません。そんなときでもunsafeなポインタは「隠蔽」可能であります。今回はCPUID読み出し。

※「GoにいればGoに従え」Go関連記事の総Index

※実機動作確認は Arm Cortex-M0+コアのRP2040チップ搭載、Raspberry Pi Pico機にTinyGoのオブジェクトを書き込んで行っています。ビルドはWindows11上です。

※RP2040のデータシート(PDF)はこちら

rp2040.go

SPIとかI2Cとか、ほとんどのマイコンが共通して持っているペリフェラルについてはmachineパッケージがその差を隠蔽してくれています。ほぼほぼ統一的なマナーでプログラミングが可能です。しかし、機種依存性が「はなはだしい」部分については直接ハードウエアレジスタを操作せざるを得ません。C言語などであればポインタの出番となります。TinyGo言語においてもポインタはありますが、そういうポインタは unsafe ということで差別されてます。しかし、自力で勝手なアドレスにアクセスするのに比べれば、事前に「中の人」が作っておいてくれたポインタ(最後のところは unsafeですが)を使う方が安心であります。

過去記事で、SAMD21マイコン上でのTinyGoでもハードウエアレジスタの直接アクセスをやってみてます。

AT SAMの部屋(9) XiaoでもGo!TinyGoでレジスタ直接アクセス、大丈夫か?

今回のターゲット機は、RP2040マイコン搭載のRaspberry Pi Pico機であるので、RP2040用のAPI関数を呼び出さねばなりません。そのファイルは以下にあります。

Tinygoのインストールディレクトリ/src/device/rp/rp2040.go

「書き変えるな」とお達しが書き込まれた2Mバイト以上のサイズのある、巨大な自動生成のソースファイルです。この中にRP2040の内蔵するハードウエアレジスタのほぼほぼ全てにアクセスできるポインタ群が含まれており、また、それを使ってレジスタ・フィールドへのアクセスするAPI関数が定義されております。

今回はこのrp2040.goを使ってみます。Picoのハードウエアに直接アクセスする第1回としてCPUIDレジスタを読み出してみます。前にも読みだしている気がするのだけれども。なにせ「読み出し期待値」は分かっているので、ちゃんと読めたことが即座に分かるという段取り。

なお、rp2040.go パッケージを使うときは importのところで以下のようにするだけで使えます。お楽。

import {
    "device/rp"
CPUIDとCHIP_ID

紛らわしいので、このボケ老人はときどきこんがらがるのです。RP2040マイコンには似た名前のハードウエア・レジスタが2個あります。

    • CPUID
    • CHIP_ID

CPUIDの方は、RP2040マイコンのコアである Arm社設計Cortex-M0+のレジスタです。Arm社が既定のもの。ソフトが走っているコアがどのようなコアであるのかをソフトウエアで判別することなどに使えます。Cortex-M0+の場合、組み込み向けのARMv6-Mというアーキテクチャであるのでシンプルです。1個の32ビットレジスタに以下の5フィールドが書き込まれてます(リード・オンリ。)

    • IMPLEMENTER、 0x41 … Armを表す
    • VARIANT、 0x0  … Revision 0
    • ARCHITECTURE、 0xC  … ARMv6-M
    • PARTNO、 0xC60 … Cortex-M0+
    • REVISION、 0x1  … Patch 1
上記のように値も判明しているので、実機上で読みだした結果を上記の値と比べれば正しく読み出せていることが分かります。なお、CPUIDレジスタは、rp2040.goファイル上では
rp.PPB
という構造体の中に含まれてます。
一方、CHIP_IDは、RP2040という「SoC」チップ全体のIDです。コアCPUだけでなく、メモリや各種の周辺回路などを含めたRP2040という半導体デバイス全体に対するもの。CPUIDがArm社規定のものであるのに対して、SoCの設計はラズパイ財団が元締めの筈なので、その御威光を示すもの。ただし、勝手なコーディングをしているわけではないようです。
JEDEC JEP-106 compliant chip identifier
ということでJEDEC登録のコードみたいです。こちらは以下のような3フィールドからなりますが、何が書き込まれているのかは読みだしたもののお楽しみっす。
    • REVISION(4bit)
    • PART(16bit)
    • MANUFACTURER(12bit)
今回実験に使ったソースコード

読み出したハードウエアレジスタのフィールド値を繰り返し表示するだけのコードが以下に。

package main

import (
    "device/rp"
    "fmt"
    "time"
)

func main() {
    cpuidIMP := rp.PPB.GetCPUID_IMPLEMENTER()
    cpuidVAR := rp.PPB.GetCPUID_VARIANT()
    cpuidARCH := rp.PPB.GetCPUID_ARCHITECTURE()
    cpuidPNO := rp.PPB.GetCPUID_PARTNO()
    cpuidREV := rp.PPB.GetCPUID_REVISION()

    chipREV := rp.SYSINFO.GetCHIP_ID_REVISION()
    chipPART := rp.SYSINFO.GetCHIP_ID_PART()
    chipMANU := rp.SYSINFO.GetCHIP_ID_MANUFACTURER()

    for {
        fmt.Println("CPUID")
        fmt.Printf("IMPLEMENTER:  %08x\n", cpuidIMP)
        fmt.Printf("VARIANT:      %08x\n", cpuidVAR)
        fmt.Printf("ARCHITECTURE: %08x\n", cpuidARCH)
        fmt.Printf("PARTNO:       %08x\n", cpuidPNO)
        fmt.Printf("REVISION:     %08x\n", cpuidREV)
        fmt.Println("CHIP_ID")
        fmt.Printf("REVISION:     %08x\n", chipREV)
        fmt.Printf("PART:         %08x\n", chipPART)
        fmt.Printf("MANUFACTURER: %08x\n", chipMANU)
        time.Sleep(time.Second * 4)
    }
}
実機実行結果

目出度くハードウエアレジスタの値は読み出し成功しているみたい。Rpico_Reg0

次回以降、ハードウエアレジスタを直接操作して何かやれよ、自分。

GoにいればGoに従え(38) ラズパイPicoでソフトウエアタイミング制御の限界? へ戻る

GoにいればGoに従え(40) ラズパイPicoの割り込みを探る、その1、イネーブルなのは誰? へ進む