背景:おちゃめなマイコン、SMT32C011J4Mが気に入って、ハンドアセンブルでプログラムに取り組む
課題:前回ADDが実行できたので、次はLチカをやってみる
結論:いろいろあったが*1Lチカはできた。どれぐらいの早さでチカチカしているのか周波数を計測したところ6MHzであった(システムクロック48MHzの1/8)
詳細:
次は周辺I/Oを触ってみるということで、Lチカをやってみる。1番ピンは、PB7という名前になっている。
これはGPIO_Bの7番と理解して、GPIO関連のレジスタのベースの番地は、0x5000_0400
GPIOx_BSRRレジスタがあって、これは該当ビットに1を書くことでGPIOの出力を0/1に制御できる
GPIOx_BSRRレジスタのオフセットは、0x18なので、GPIOBの出力を0/1したかったら、0x5000_0400 + 0x18の番地にマップされている、GPIOB_BSRRの7bit(BS7)と7+16bit(BR7)に1を書きこめば1/0制御できるはず
gdbで特定メモリの番地に値を書くには、set命令を使う
以下でBS7をセット (PB7がHになるはず((GPIOブロックにクロックを供給しろとか言う仕様でなければ)))
set {int} 0x50000418 = 0x00000080
以下でBR7をセット (PB7がLになるはず)
set {int} 0x50000418 = 0x00800000
やってみたが、ポートはH/Lせず。操作する前からLEDがうっすら光っているのが怪しい。INPUTモードになっているというオチは?
GPIOBのレジスタをダンプすると以下
(gdb) x/11xw 0x50000400 0x50000400: 0x0000a000 0x000000c0 0x0000f000 0x00005000 0x50000410: 0x000000c0 0x000000c0 0x00000000 0x00000000 0x50000420: 0x66000000 0x00000000 0x00000000
0x5000_0400の値が 0x0000_a000なので、GPIOB_MODERと見比べると、MODE7が0b10、MODE6が0b10となっている。0b10とはAlternate functionなので、少なくとも普通のGPIOとしては設定されていない。よって、GPIOB_MODERのMODE7を0b01に設定する必要あり
以下でMODE7を0b01に設定、MODE6は設定を変えたくないので0b10を上書き
set {int} 0x50000400 = 0x00006000
(gdb) x/xw 0x50000400 0x50000400: 0x00006000
値が変わったことを確認。(alternate functionからOUTPUTに変えたのにLEDはうっすら光っている。失敗の予感)
再度以下でLチカをやってみた。一応点滅はする。が、、LEDはめちゃ暗い。リーク電流で光っているぐらいに暗い。
(gdb) set {int} 0x50000418 = 0x00000080 (gdb) set {int} 0x50000418 = 0x00800000 (gdb) set {int} 0x50000418 = 0x00000080 (gdb) set {int} 0x50000418 = 0x00800000
仕様書に書かれているリセット後デフォルト設定に頼って、GPIOのレジスタ値をほとんど設定していないのがまずいのかも。
■追記
この後間違ってマイコンの電源を抜いてしまってからまったく思うような動きにならなくなった。なぜだろうか
(正確にはLEDにつながっているGNDと思って、試しに3.3Vにつないだ。それはマイコンのGNDだった。この時、LEDは明々と光った。GPIOのブロックを壊してしまった?)
■追記
マイコンは2つ買っていたので、もう一つの方を基板に貼り付けて動かしてみた。結果は以下
(gdb) x/11xw 0x50000400 0x50000400: 0x0000a000 0x000000c0 0x0000f000 0x00005000 0x50000410: 0x000000c0 0x00000000 0x00000000 0x00000000 0x50000420: 0x66000000 0x00000000 0x00000000
初期値はAlternativeになっていると。OUTPUTに変更
(gdb) set {int} 0x50000400 = 0x00006000 (gdb) x/11xw 0x50000400 0x50000400: 0x00006000 0x000000c0 0x0000f000 0x00005000 0x50000410: 0x00000040 0x00000000 0x00000000 0x00000000 0x50000420: 0x66000000 0x00000000 0x00000000
以下でLHしてみる。やはりうっすら光っている。逆流しているのか?と思うぐらいに薄い
(gdb) set {int} 0x50000418 = 0x00000080 (gdb) set {int} 0x50000418 = 0x00800000 (gdb) set {int} 0x50000418 = 0x00000080
次また同じ絵が撮れるか分からないので記念に撮影
GPIOのレジスタマップのうち、どれかが思った値になっていないと思われる。初期値に頼らず、正しい値にすれば動くと思われる。全てのビットを突き合わせる必要あり
■追記
とりあえずリセット初期値を設定かと思って、以下の設定したらマイコンが暴走したような気がする(OpenOCDは通信エラーが出た)
set {int} 0x50000404 = 0x00000000
Hに設定した時の電圧は1.7Vで、Lに設定した時の電圧は0Vであった。なんでこんな中途半端(3.3V/2)な電圧か??と思っていたのだが、レジスタを再確認すると、GPIOB_OTYPERにより、GPIO_Bの7番はOpenDrainに設定されていた。だから、Lの時はGNDに接続されて0Vになるが、Hの時はHi-Zなのではなかろうか。だとすると、PullUpすれば点滅するはず。あるいは、レジスタをOpenDrainからPushPullに変更すると。いずれかの方法で正しく点滅すると期待。
■追記
回路に手を入れて、Pin1とLEDの間に3.3KのPullUpを追加、この状態で、以下の作業を行ったところ、LEDの点灯、消灯を確認できた
(gdb) set {int} 0x50000400 = 0x00006000 (gdb) x/11xw 0x50000400 0x50000400: 0x00006000 0x000000c0 0x0000f000 0x00005000 0x50000410: 0x00000040 0x00000000 0x00000000 0x00000000 0x50000420: 0x66000000 0x00000000 0x00000000 (gdb) set {int} 0x50000418 = 0x00000080 (gdb) set {int} 0x50000418 = 0x00800000
次はPull抵抗をつけずに、Pinの仕様をOpen Drainから、Push Pullに変えて、どうなるかを確認する。
現在は0xc0なので、OT7, OT6がOpen Drainになっている。
(gdb) x/xw 0x50000404 0x50000404: 0x000000c0
OT6は変更したくないので、0x40を書き込む。
set {int} 0x50000404 = 0x00000040
設定後確認
(gdb) x/xw 0x50000404 0x50000404: 0x00000040
LEDチカチカさせてみる
set {int} 0x50000418 = 0x00000080 set {int} 0x50000418 = 0x00800000
LEDは普通に明るく光る<->消える、となった。ここまではgdbから手動でGPIOレジスタを操作していたので、これを機械語でやりたい。SRAM領域に手動でアセンブルした機械語を書きこんで、実行させたい。機械語によるLチカなので、SystemClockが48MHzなら1/5~1/6ぐらいの速度で回ると期待。(8MHzでLチカになるのでは)
ただ、ARMの場合、レジスタへのイミディエートMOVEが8bit長らしく、今回のような32bit長だと4回MOVEするか、何か手を考えないといけない。
二―モックで言うところの、以下を使ってGPIOのレジスタに定数を書き込む。右側のコメントは機械語のHEX値
STR R1, [R0] # 6001 STR R2, [R0] # 6002
上記プログラムは、R0:GPIOレジスタの番地、R1:GPIOをHにする即値、R2:GPIOをLにする即値が入っているのを前提としている。
これをRAMの先頭から、gdbで書き込む。逆アセンブルすると以下。正しい機械語が書かれていると判断。
(gdb) x/2i 0x20000000 0x20000000: str r1, [r0, #0] 0x20000002: str r2, [r0, #0]
本来のプログラムなら、レジスタに書き込む32bitの即値(Immediate value)の設定もプログラム内で実行すべきだが、あまりに手間なので、gdbのコマンドでレジスタに値を書き込む(ここはチート)
(gdb) i r r0 0x50000418 1342178328 r1 0x80 128 r2 0x800000 8388608 *略*
pcを0x20000000に設定して一行ずつ走らせると、点灯->消灯になるはず。以下の実行により点灯、消灯となった。
(gdb) set $pc = 0x20000000 (gdb) ni 0x20000002 in ?? () (gdb) ni 0x20000004 in ?? ()
これに分岐を加えると、HLを繰り返して高速な点滅が可能になる。BALは上位5bitが11100、下位11bit(n11)で命令数を指定するのだが、、若い番地に飛ぶのにどうしたらいいのか? n11は符号付数値なのか??
仮に符号付だとして、-1となるように、 機械語として、0xE7を0x2000006に書き込んでみる
(gdb) set {short} 0x20000004 = 0xe7ff (gdb) x/3i 0x20000000 0x20000000: str r1, [r0, #0] 0x20000002: str r2, [r0, #0] => 0x20000004: b.n 0x20000006
次のアドレスに飛ぶだけであった。マイナス度合いが足りない?
(gdb) set {short} 0x20000004 = 0xe7fc (gdb) x/3i 0x20000000 0x20000000: str r1, [r0, #0] 0x20000002: str r2, [r0, #0] => 0x20000004: b.n 0x20000000
0x7FCとすると、4番地から0番地まで戻るようであった。あとから時間をかけて理解するとして、、これでループするのか、ni(next instruction?)を何度か打って確認する。
以下の通り、0番地->2番地-> 4番地->0番地を繰り返している(長くなるので上位アドレスは略)
(gdb) ni 0x20000000 in ?? () (gdb) ni 0x20000002 in ?? () (gdb) ni 0x20000004 in ?? () (gdb) ni 0x20000000 in ?? () (gdb) ni 0x20000002 in ?? () (gdb) ni 0x20000004 in ?? () (gdb) ni 0x20000000 in ?? () (gdb) ni 0x20000002 in ?? () (gdb) ni 0x20000004 in ?? () (gdb) ni 0x20000000 in ?? ()
これで、c(continue)を打てばずっと回るはず。。LEDは光り続けている(単に暴走しているだけ?)
Ctrl-Cで止めてPCを見ると、4番地(0x2000_0004)なのでOK
(gdb) c Continuing. ^C Program received signal SIGINT, Interrupt. 0x20000004 in ?? () (gdb) i r r0 0x50000418 1342178328 r1 0x80 128 r2 0x800000 8388608 r3 0x66000000 1711276032 r4 0x20000360 536871776 r5 0x40005400 1073763328 r6 0xffffffff -1 r7 0xffffffff -1 r8 0xffffffff -1 r9 0xffffffff -1 r10 0xffffffff -1 r11 0xffffffff -1 r12 0xffffffff -1 sp 0x20000d80 0x20000d80 lr 0x1fff0cc1 536808641 pc 0x20000004 0x20000004 xpsr 0x1000000 16777216 msp 0x20000d80 0x20000d80 psp 0xfffffffc 0xfffffffc primask 0x1 1 basepri 0x0 0 faultmask 0x0 0 control 0x0 0
再度cで走らせて、オシロでどれぐらいの早さでループしているかを確認
以下の通り、山の間隔は164nsで、周波数は6MHzであった。システムクロックが48MHzなので、1/8まで速度が落ちている。渋めに見積もって(?)8MHzかと思っていたけど、さらに下がって6MHzであった。
マシンサイクルが示されていて、ブランチは3サイクル、ストアは2サイクルかかると。今回のプログラムはストア2回、ブランチ1回なので、計算上は、7サイクルかかるはずだが、+1どこかでかかっているということか。。
ループ実行までできたけど、全て手打ちで再現性が低い。次回も同じことができると思えないので、今日の最終形をまとめる
# set value to registers set $r0 = 0x50000418 set $r1 = 0x00000080 set $r2 = 0x00800000 # write machine instructoins to SRAM set {short} 0x20000000 = 0x6001 set {short} 0x20000002 = 0x6002 set {short} 0x20000004 = 0xe7ff # check assemble code x/3i 0x20000000 # set PC set $pc = 0x20000000 # continue c
なお、上記プログラムが正常に動作するには、gdbのメモリ操作コマンドを使って、GPIOのレジスタにおいて、GPB7をOUTPUTにして、PUSH PULLに設定する必要あり
■追記
機械語一覧に分岐の数の数え方が書かれている。命令差分に対してさらに-2とある。2つ前に戻るには命令差分としては-2、さらに-2するから-4と。0x7FCは11bit長の符号付き整数とすると、-4。一応つじつまは合う。
■参考URL
Cortex-M0 ARMマシン語表(抜粋)
*1:仕様書斜め読みというが主な原因で