Raspberry Pi pico 解析(core1 wait and wakeup...)

まだ少ししか触ってませんが、今まで使用してきたプロセッサはcore0で、core1は使ってないです。Raspberry Pi picoは Coretex-M0+ dual core なので片側(core1)を使っていないのはもったいない。そこでcore0とcore1がどのように共存しているのか、core1を使うにはどのようなことが必要かを調べてみました。core0とcore1の共存という事で、RP2040 Datasheetの Figure 6 に大まかなブロックが書かれています。Figure 7 ではSIOに注目してブロックが書かれています。

rpi_pico_rp2040_datasheet_fig6.png

rpi_pico_rp2040_datasheet_fig7.png


RP2040 Datasheet の 2.3.3. Event Signals に 「event信号はプロセッサ間で相互に接続されている」とあるので、core0でSEVするとcore1のWFEで受け、core1でSEVするとcore0のWFEで受けるという構成になっている様です。なので、自分自身(プロセッサ)にSEVを投げることはできないのですね。

プロセッサ間の通信ではSEV,WFEの構成をうまく利用しています(そのためにこの構成なのでしょうが)。もちろん、この通信方法は編集できないプログラム領域(ROM)で定義されている箇所にいるときだけです。後述しますが、core1が最初にROM内のプログラムによってWFE待ちの状態になってますが、これを解除した後は外部Flashや内臓SRAM上のプログラムになるので、プロセッサ間通信の方法は作り手しだいでいか様にも構成できます。


パワーオンリセット後にcore1が実行する場所はどこになるのでしょうか? RP2040 Datasheetの 2.4.8. List of Registers の M0PLUS: VTOR Register の内容より、VTOR.TBLOFFは初期値0x000000なのでcore0もcore1もリセットベクターは0x00000004に書かれているアドレスへジャンプするようです。bootromのソースコードを追っていくと、リセットベクター先に飛んで初っ端にcore0 or core1を判断しています。

rpi_pico_rp2040_datasheet_vtor.png

core0の場合はいろいろと複雑なことをした後、以前記載した様にROM→SRAMへ実行場所が移動しています。一方、core1の場合は最終的にWFEで待ちになってます。core1の処理内容を追ってみます。

core1はパワーオンリセットすると0x000004のリセットベクターが示す先(_start)に飛びます。_start でcoreのCPUIDを調査して0以外ならwait_for_vectorへ更に飛びます。wait_for_vectorへ飛んだ後は下図のようなシーケンスになるようです。図は流れがわかる範囲で簡素化しています。アセンブリ言語で書いてあるのと、エラー処理が入っているので実際はもっと複雑になってます。

rpi_pico_wait_core1.png

core0が何もしてこなかった場合、core1は以下の処理をします。

1. RX_FIFOを空にする
2. TX_FIFOに0を書き込む
3. core0に対してイベントを発生させる(SEV)
4. core1に対するイベント待ちにする(WFE) ← DEEPSLEEP

つまり、core1はcore0がイベント発生(SEV)するまでDEEPSLEEP状態で待ち続けているのです。DEEPSLEEP状態なので低消費。意図的にSEVしない限り悪さもしない。良いですね~。

では、眠り続けているcore1を起こして意図したプログラムを実行してもらうにはどうするかですが、core0からSEVでcore1を起こして、実行先を示してやるのです。上図では「val毎の処理」として端折ってますが、決まったシーケンスでcore0から指示を出してやる必要があります。また、1回の指示毎にWFEに遷移するので毎回core0からSEVを発行する必要があります。core0からの指示内容は以下の順です。

1. TX_FIFO(core0→core1)に'1'を書き込みSEVを発行する
2. TX_FIFO(core0→core1)にcore1のベクターアドレスを書き込みSEVを発行する
3. TX_FIFO(core0→core1)にcore1のスタックポインタ設定先を書き込みSEVを発行する
4. TX_FIFO(core0→core1)にcore1が実行するアドレスを書き込みSEVを発行する

C/C++ SDKでは途中、core1が予期しない位置にいる場合もあるので先頭へ戻す意味も含めて上記の1~4の前に'0'を書き込んでSEVを発行するを2回繰り返しています。

注目するべきは、core1がcore0に指示された先にジャンプするまでの処理は全てROM領域に書かれている内容であるという事です。なので、core1を起動させるにはこの手順を守らなければならないです。ただし、一旦飛び出すとそれからは自由です。再度core1をWFE(WFI)で寝させる場合、その後の起動方法はユーザーのプログラム次第なのです。プロセッサ間の通信も自由に設計できます。
スポンサーサイト



tag : RaspberryPi_pico

Raspberry Pi pico 解析(bootrom version)

OpenOCDでデバッグ繋げてinsightを繋いでいましたが、どうもROM領域にPCがあると正常に表示してくれません。insightの仕様だと思うのですが、有料のデバッガの様にソースコードが無い領域でも逆アセンブルして表示してくれるなんて便利なことはやってくれないみたいです。そこで試しにinsightのgdbコマンドウィンドウでROM領域のデータを覗いたところ普通に見ることができたのでファイルに落としてobjdumpで逆アセンブルしてみました。

(gdb)dump binary memory bootrom.bin 0x000000000 0x000004000
$ arm-none-eabi-objdump -D -b binary -m arm  -M force-thumb ./bootrom.bin > ./bootrom_asm.txt


逆アセンブルできているのは確認してご満悦だたのですが、上から順に見ていると見覚えの無い数字がでてきたので あれっ と思いました。そう、Bootrom version が "1" なんです。データシートの 2.8.3. Bootrom Contents にこの辺りのフォーマットが載ってます。

rpi_pico_bootram_version.png

githubに上がっているmasterはTagのb1(version=2)と同じでその前がb0(version=1)になってます。つまり私が入手した Raspberry Pi pico は(バージョン情報だけ見ると)b0相当のbootromが書かれている物だと言うことです。b1とb0の差分内容は正直よくわからないですが、今後のロットではb1の方が出るのかもです。

tag : RaspberryPi_pico

Raspberry Pi pico 解析(Bootloader 2nd stage at C/C++ SDK)

前回 bootrom で行っている内容をサラッと記載しました。今回は C/C++ SDK を使った時の最初の動作である Bootloader 2nd stage を追っていこうと思います。

bootrom で 256byteのデータを外部Flashから内蔵RAMの0x20041F00~0x20041FFFにコピーされて、プログラムの実行場所がその先頭の0x20041F00になる。Bootloader 2nd stage はここから始まる。これは Bootloader のリンカスクリプトからも分る。Bootloader 2nd stage のソースコードはというと、boot2_w25q080.S。ここで行っている内容は以下。

1. XIPで外部FlashとQSPIで繋ぐ
2. ベクターテーブルを 0x10000100 として登録する(PPB.VTOR)
3. MSR(main stack pointer)に 0x10000100 番地の値(stack pointer address)を設定する
4. 0x10000104 番地(リセットベクター)で指定されている先へジャンプする

0x10000100以降の内容はベクターテーブル+プログラムです。cortex-Mxを触っている人にとってはお馴染みの内容でしょう。

C/C++ SDK付属のcmake設定ファイル群ではこれらを実現するためのパッキング(コンパイルとCRC32の実行、プログラムの結合)を行っています。パワーオンリセットからの実行プログラム位置の概要としては下図のような感じ。①と③はROM、外部Flashそれぞれのベクターテーブルに入っているリセットベクターが示す位置になる。

rpi_pico_boot_2nd_sc.png

ぶっちゃけ、プログラム全体が252byteで足りるのなら、bootloder 2nd stage なんていらない。ってことでお試ししてみましょう。ちょうど良い所でdwelch67さんのプログラムを見つけました。252byte以下で 2nd stage 無しで実行することを目的とされたものです。リンカスクリプトを見ると、XIP領域への配置になっているのでbootromの解釈を間違えたかな~と思ったが、blinker00を書き込んで実行すると以下の様にSRAM内で実行されていました。

> mdb 0x20041f00 256
0x20041f00: 05 48 85 46 00 f0 0a f8 fe e7 01 60 70 47 00 68 70 47 01 38 fd d1 70 47 00 10 00 20 10 b5 20 21
0x20041f20: 22 48 ff f7 f2 ff 20 24 21 48 ff f7 f0 ff 04 42 fa d0 80 21 80 24 1d 48 49 00 ff f7 e6 ff 64 00
0x20041f40: 1b 48 ff f7 e4 ff 20 42 fa d0 80 21 19 48 89 04 ff f7 db ff 80 21 18 48 89 04 ff f7 d6 ff 17 48
0x20041f60: ff f7 d5 ff 40 21 48 40 80 31 01 40 14 48 ff f7 cc ff 05 21 13 48 ff f7 c8 ff 80 21 12 48 89 04
0x20041f80: ff f7 c3 ff 80 21 11 48 89 04 ff f7 be ff 80 20 40 03 ff f7 be ff 80 21 07 48 89 04 ff f7 b5 ff
0x20041fa0: 80 20 40 03 ff f7 b5 ff ec e7 c0 46 00 f0 00 40 08 c0 00 40 28 00 00 d0 18 00 00 d0 68 c0 01 40
0x20041fc0: 68 d0 01 40 cc 40 01 40 24 00 00 d0 14 00 00 d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x20041fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 fa 60 33 2f

> mdb 0x10000000 256
0x10000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100000a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100000c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100000e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

> resume
> halt
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x00000178 msp: 0x20041f00
target halted due to debug-request, current mode: Thread
xPSR: 0x21000000 pc: 0x20041f12 msp: 0x20000ff8

> targets
TargetName Type Endian TapName State
-- ------------------ ---------- ------ ------------------ ------------
0* rp2040.core0 cortex_m little rp2040.core0.cpu halted
1 rp2040.core1 cortex_m little rp2040.core1.cpu halted


dwelch67さんのプログラムのままだとデバッグ情報と実行位置が不一致になるのでリンカスクリプトを編集して配置を0x20041F00からにして.binファイルを生成し、252byteに揃え、CRC32を付加します。パディングとCRCの付加はSDKのpythonファイルを編集して作成しました。このpad_checksumはRPi-pico用に作られているはずなのにCRCの初期値(0xFFFFFFFF)をオプションで指定する必要があるという謎仕様で小1時間ほどはまってしまいました。この内容はFlashに書き込む必要があるので0x10000000へ直接書き込みます。

$ make
$ pad_checksum_bin -s 0xffffffff notmain.bin notmain_pad_crc.bin


rpi_pico_blink00_sram_write.png

これでblinker00が動きます。

アドレスが合っているelfファイルを生成したので、お次はデバッガと行きたいところなのだが、ちょっとトラブル発生でinsightを使ったデバッグはお預けです(シンボル入りのASMは表示するけどCファイルを表示してくれないとか、挙動がおかしいとか…)。-gオプション付けてなっただけでした。insight使ってデバッグ中の画面貼り付けておきます。

rpi_pico_blink00_sram_insight.png

tag : RaspberryPi_pico

Raspberry Pi pico 解析(bootrom)

Raspberry Pi pico(以後 RPi-pico)のMPUであるRP2040は内蔵ROM:16KByte, 内蔵SRAM:264KByteを持っています。内蔵ROMはあくまでも起動用で書き込み不可、メインのプログラムはQSPIで繋げた外付けのFlashを使用する構成です。Cortex-M0+ なのに Cortex-A クラスの様な生意気な構成ですね。こういった構成の場合、内蔵ROMの中に普段使い慣れたような配置のプログラムが書かれていて、それが外付けFlashにアクセスして起動するのです。RP2040もそういった構成。なので、内蔵ROMに書かれたブートローダーの仕様に合わせて外付けFlashにFirmwareを書き込む必要があります。uf2形式で書き込むときも考え方は同じです。

rpi_nano_bus.png

通常内蔵ROMの仕様は小出しか全く隠蔽されているかなのですが、RPi-picoのすごいところは、ブートローダーのソースコードが丸々公開されています。なので、詳細を確認したい人はソースコードを追っかけてみてください。ここではRP2040 Datasheetに書かれている内容とブートローダーのソースコード(少し)を紐解いた内容をもとに電源ONからユーザーが書き込んだプログラムにたどり着くまでどのような動作をするのかを記載します(前もって言っておきますが内容は薄いです)。

こんな感じです。

rpi_pico_boot_sc.png

②は正確には 2nd stage で行うのでここで記載しているブートローダーの仕事では無いですが、C/C++ SDKの構成ではこうなっているよという意味で描いてみました。

RP2040は電源ON直後に0x00000004番地(内蔵ROM)のリセットベクターを参照してブートローダーを実行します。ブートローダーの仕事は主に2つ。外部Flashから内容を読みだしてSRAMにコピーしてジャンプする事と、USB-MSCによって.uf2ファイルを書き込む事。後者は説明を省きます。前者を大雑把に書くと以下の内容です。

1. SPIモードで外部Flashの先頭256byteを内蔵SRAMのRAM5の末尾256byteにコピーする
2. CRC32チェックする
3. コピーした先へジャンプする

RP2040 Datasheetのフローではこうなってます。
(ブートローダーは processer core 0 のみで動いています)

rpi_pico_bootrom_sc.png

内蔵SRAMは0~5まであって、0~3はちょいと複雑な構成になってますが、SRAM4,5はこんな感じにそのまま配置されてます。

rpi_pico_sram45_addr.png

これより、SRAM5は0x20041000~0x20041FFFということが分かりますね。これの末尾256byteですから…。0x20041F00~0x20041FFFにコピーされます。これはC/C++ SDKのリンカスクリプト(pico-sdk/src/rp2_common/boot_stage2/boot_stage2.ld)からも明らかですね。C/C++ SDKではこの後に 2nd stage としてXIP機能を有効にしてQSPIの領域をメモリマップドの様に扱える設定をしています(実際はQSPIの命令が飛び交っています)。ブートローダーではベクター領域の設定をそのままにしてジャンプするだけなので、2nd stage でベクター領域を置き換えています。

ブートローダーの仕様からその後に用意しなければならないことは以下になりそうです。

1. 先頭からプログラムを記載する(ベクター領域ではなく動く部分です)
2. 252byteでプログラムを完結させるかXIPの設定をしてジャンプするかを行う
3. ベクター領域を再設定する
4. processer core 1 を起動させる?

1~3はC/C++ SDKを推奨仕様のまま使用される場合はSDK内で自動的に生成される部分です。

tag : RaspberryPi_pico

自作アプリに Google Map を表示させる(Google Mapからの通知受け)

Google MapはJavaScript上で勝手に動いてくれてますので地図を移動されようが、拡大・縮小されようが表示するだけならアプリ側では何もケアする必要はありません。ただ、少しアプリで何かしようとすると「勝手に動いてくれる処理」が邪魔になったりします。その辺り、Google Map側も身勝手ではないのできっかけは用意してくれてます。今回は最も基本となりそうな中心座標をアプリで取得してみようと思います。

中心座標を取得する切っ掛けを Google Maps API から発行してもらいます。というのも、Google Mapをマウスで掴んで素早くスクロールさせ離した場合、マウスは離した状態なのにMapは惰性でスルスルと動いているというケースがあるのでWebBrowserコントロールのクリックイベントのみでは止まった位置を把握する事が難しいからです。

Mapの移動が終わった時に発生するイベントは 'idle' です。ただしこの時点ではMap画像の読み込みは必ずしも終わっているわけではなく、そっちを捕まえたい場合は 'tilesloaded' を使用すると良いかもです(使った事無い)。以下の様にJavaScriptに記述すると'idle'のイベントが発生した時にこの関数読んでね的な事ができます。

map.addListener('idle', gm_event_idle);


gm_event_idle()はJavaScript内の関数(にしておく)なのでそこから一気にWindowのFormの関数を呼び出す様にします。これには、JavaScriptとWebbrowserコントロールとFormの連携が必要になってきます。window.external と WebBrowser.ObjectForScriptingプロパティ と 実行関数ですね。code.jsファイルは埋め込みリソースにしてgmap.htmlにキーワードを入れていてそこへ置換機能を使って展開しています。前回実施したのと同じ方法です。

code.js
function initMap() {


map.addListener('idle', gm_event_idle);


}

function gm_event_idle(){
var latlng = map.getCenter();
var str = latlng.lat() + "," + latlng.lng();
window.external.GMEvent_idle(str);
};


Form1.cs
[System.Runtime.InteropServices.ComVisible(true)]
public partial class Form1 : Form
{
private void FormMain_Load(object sender, EventArgs e)
{


this.webBrowser1.ObjectForScripting = this;


}


public void GMEvent_idle(string message)
{
string[] center = message.Split(',');
SetTextLatLong(double.Parse(center[0]), double.Parse(center[1]));
}

private void SetTextLatLong(double latitude, double longitude)
{
double fractional = latitude;
int integer = (int)fractional;
fractional -= integer;
string stInt = string.Format("{0,3:##0}", integer);
string stFrac = string.Format("{0:.0#############}", Math.Abs(fractional));
string st = stInt + stFrac;
this.maskedTextBoxLatitude.Text = st; // 緯度


fractional = longitude;
integer = (int)fractional;
fractional -= integer;
stInt = string.Format("{0,3:##0}", integer);
stFrac = string.Format("{0:.0#############}", Math.Abs(fractional));
st = stInt + stFrac;
this.maskedTextBoxLongitude.Text = st; // 経度
}



}


WebBrowser.ObjectForScriptingプロパティに設定するオブジェクトはComVisible属性を持つ必要があります。なので、class Form1 の直前に属性定義を記載しています。SetTextLatLong()の所でmaskedTextBoxコントロールに対して値を表示する様に設定しています。小数点の位置を保ちたかったのと、小数点第2位以下は0なら空白にしたかったのでチョッと変な変換を噛ましてます。

ビルドして実行した結果がこちらの動画です。
位置が決まると画像を読み込む前に緯度・経度を表示しているのと、画像を読み込む前に緯度・経度を表示しているのが見て取れると思います。


tag : windows

tag : map

黒ねこ時計 くろック D02
プロフィール

jujurou

Author:jujurou
運営HP:チャコの部屋
Twitter:jujurou

カレンダー
04 | 2021/05 | 06
- - - - - - 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 - - - - -
最新記事
最新コメント
カテゴリ
ユーザタグ

ぺるけ Linux RaspberryPi 全段差動プッシュプル 6P43P-E 三段化 トランジスタ式ミニワッター 6P43P TRminiWatterPart4 RaspberryPi_pico MPD FON2405E イーサネットコンバータ OpenOCD DAC buildroot windows Bluetooth BeagleBoneBlack FM3ペリフェラル map ODROID-U2 FM3評価ボード library mingw OpenGL シングル bitbake KiCad 計測 プリアンプ Edison VMware FM4 ミニワッター 6N6P TL-WR700N 

月別アーカイブ
ランキング

FC2 Blog Ranking

カウンター
検索フォーム
リンク
RSSリンクの表示
QRコード
QRコード
ライセンス
クリエイティブ・コモンズ・ライセンス
Twitter