買ってしばらく放置してたNucleoのSTM32F446REボード、ちょっとやりたい事があって使ってみることにしました。
前回(
5/31 マイコンボードいっぱい買った①~Nucleoボード動作確認)の時の結論では、
「とりあえずオンラインmbedの方がarduinoライクに簡単にプログラムが書けそうだけど、もっと詳細な設定をしたければ、cubeMXで設定作ってmbedに必要な部分だけコピペするか、「cubeMX+SW4STM32」で作った方がいい」
となったのですが、mbedのオンラインコンパイラーは、どうもできる事が限られてる気がする…。
アセンブルリストを確認したいのだけど、どうしても出し方が分からない…。
だいぶ探した後、「エクスポートして別のオフライン環境で開発する」という作戦があるとの事。見てみると、
![](https://blogimg.goo.ne.jp/user_image/29/2b/97aa9e6711fee639e4ae119322fe0538.png)
e2studioがある!SW4STM32もある!
e2studioはルネサスのRX621ボードの為にインストールしてちょっと使ったので使いやすい。SW4STM32は、一応入れたけど、まだあまり使ってないw。
エクスポートして開いてみたけど、どちら(e2studio/SW4STM32)もprintfがエラーになってビルドできない。printfを全部コメントアウトしても’undefined reference to _wrap_vsnprintf’とやらのエラーが出て、どうしてもビルドできない…。
いろいろキーワードで検索してもなかなか解決せず、最終的にいつも親切なルネサスのフォーラムでヒントを見つけて、e2studioで何とかビルドできて、アセンブルリストも見れるようになりました。
(具体的には、プロジェクトのルートにあるファイル’.cproject’の中から、エラーの出る_wrap_??printfを含む行を削除していったら、ビルド通るようになりました。)
英語にはそこそこ自信があるのですが、e2studioはフル日本語で、やっぱり助かる~ww。
プログラムは前回の「クロックを設定して、クロックの各設定値をprintfで出力した後、Lチカする」というプログラムですが、「mbedで作ったプログラムはサイズがデカい!」という情報だったので、プログラムサイズ見てみます。
![](https://blogimg.goo.ne.jp/user_image/17/43/282d96b0d73f90002394d7873eeba38e.png)
やはり、クロック・GPIOの設定して表示してからLチカするだけのプログラムで、42KBもありますね…。まあ、これから手書きでプログラムのライン数を増やしていっても、比例で増えてくことは無いと思いますけど。
「
mbed のバイナリサイズを減らす方法」という記事によると、printf不使用・実行時アサート無効にするとかなり小さくできるとの事ですが、今のところはprintfが使えた方が都合がよさそうだし、メモリには余裕があるので、とりあえずこのままとします。
このプログラムで、アセンブルリストを確認・できる範囲で解読を試みますw。
前半のHALでクロックやGPIOの設定・初期化をしてるところは省略して、Lチカのところだけ貼り付けます。(一部加工してます。)
42 0008 4B4C ldr r4, .L4
159 00c8 254E ldr r6, .L4+40
65:../main.cpp **** RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
190 .loc 1 65 0
191 00fc 236B ldr r3, [r4, #48]
192 00fe 43F00103 orr r3, r3, #1
193 0102 2363 str r3, [r4, #48]
66:../main.cpp **** GPIOA->MODER |= 0x5555;
194 .loc 1 66 0
195 0104 3368 ldr r3, [r6]
196 0106 43F4AA43 orr r3, r3, #21760 // 0x5500
197 010a 43F05503 orr r3, r3, #85 // 0x55
198 010e 3360 str r3, [r6]
67:../main.cpp **** GPIOA->OSPEEDR |= 0xAAAA;
199 .loc 1 67 0
200 0110 B368 ldr r3, [r6, #8]
201 0112 43F42A43 orr r3, r3, #43520 // 0xAA00
202 0116 43F0AA03 orr r3, r3, #170 // 0xAA
203 011a B360 str r3, [r6, #8]
204 .L2:
68:../main.cpp ****
69:../main.cpp **** for(;;) {
70:../main.cpp **** GPIOA->ODR = 1;
205 .loc 1 70 0 discriminator 1
206 011c 104C ldr r4, .L4+40
207 011e 0123 movs r3, #1
208 0120 6361 str r3, [r4, #20]
71:../main.cpp **** wait(0.2);
209 .loc 1 71 0 discriminator 1
210 0122 1048 ldr r0, .L4+44
211 0124 FFF7FEFF bl wait
212 .LVL15:
72:../main.cpp **** GPIOA->ODR = 0;
213 .loc 1 72 0 discriminator 1
214 0128 0023 movs r3, #0
215 012a 6361 str r3, [r4, #20]
73:../main.cpp **** wait(0.5);
216 .loc 1 73 0 discriminator 1
217 012c 4FF07C50 mov r0, #1056964608
218 0130 FFF7FEFF bl wait
219 .LVL16:
220 0134 F2E7 b .L2
221 .L5:
222 0136 00BF .align 2
223 .L4:
224 0138 00380240 .word 1073887232 // 0x40023800
225 013c 00000000 .word .LC0
226 0140 14000000 .word .LC1
227 0144 2C000000 .word .LC2
228 0148 44000000 .word .LC3
229 014c 5C000000 .word .LC4
230 0150 74000000 .word .LC5
231 0154 8C000000 .word .LC6
232 0158 A4000000 .word .LC7
233 015c BC000000 .word .LC8
234 0160 00000240 .word 1073872896 // 0x4002000
235 0164 CDCC4C3E .word 1045220557 // 0x3E4CCCCD |
一番前の2行は、前のほ~うから探して取ってきました。
HAL関数の実装やwaitは、このmain.o.lstには含まれていない様です。
RCCの設定用にr4=0x40023800、GPIOの設定用にr3=0x4002000を使っている様です。
そして、offsetを使ってstrでメモリ上に書き込んでます。
(これは、PICのバンク切り替えに似てるかもしれません。「今からGPIOAに関する操作をするよ」という事で、「メモリ・アドレスのベースとなるレジスタ」をセットしてからGPIOAを操作し始める感じです。)
まず、先頭の番地を指定します。
![](https://blogimg.goo.ne.jp/user_image/55/6b/d5f2be5b39316201d5a3f003246f50f0.png)
次に、各設定のoffsetに従って、代入します。
![](https://blogimg.goo.ne.jp/user_image/56/67/492a349d9f209e9b226ba23fd6b5ea03.png)
元のプログラムでは、例えば「GPIOA->MODER |= 0x5555;」で1行であたかも即値代入ですが、or取ってる事もあって1命令では済んでませんが…、orが2つに分かれてます…。
なんででしょう?データシートを調べます。
![](https://blogimg.goo.ne.jp/user_image/60/3c/4d18c51cba1d6e266be82df8d6fa96f5.png)
「フレキシブル第2オペランドについては59ページを見てね」との事w。
![](https://blogimg.goo.ne.jp/user_image/2e/dd/6c50c2280e160f947d1eec70b9169adb.png)
(日本語のデータシートがゲットできて助かった~ww)
なるほど~。定数で指定できるのは0x55等の8ビットだけだけど、シフトして0x5500なら指定できるのね。ニーモニック的には区別つかないけど、コードに変換されるときに8ビット+シフト数がコードに入れ込まれるんでしょうね。
できるなら、急ぐポート出力には、orを使わずに代入した方がいいですね。
ポートへの出力(メモリへの代入)も即値でできず、"0"と"1"をレジスタに代入してからストアしてます。それとアドレスベースの代入もループの中に入ってるので、それらをループの外に出せば、速度が上がりますね。
このアセンブラリストを手掛かりに、インラインアセンブルを使って、GPIOの最速でのON/OFFを目指してみます。
プログラムの必要部分だけ書き出すと、こうなりました。
int high, low
high = 1;
low = 0;
asm volatile (
"ldr r4, .List \n\r"
".Label1: \n\t"
"str %[H],[r4,#20] \n\r"
"str %[L],[r4,#20] \n\r"
"b .Label1 \n\r"
".List: \n\r"
".word 0x40020000 \n\r"
:
:[H] "r" (high),
[L] "r" (low)
: "r4"
); |
high=1,low=0は、アセンブルの中のループの外にアセンブラで指定してもよかったのですが、今回は拡張インラインアセンブラで、C側から変数を渡す書式にしました。
元のCを参考にして、ラベル:.Listに書いてあるwordを参照させてr4にGPIOのアドレスを設定し、そのアドレスに値(汎用レジスタ)をストアする形になりました。
(最初、「mov r4,#0x40020000」としましたが、ダメでした。movの第2オペランドでの即値は16ビットまででした。)
アセンブル結果はこうなってます。
71:../main.cpp **** high = 1;
72:../main.cpp **** low = 0;
73:../main.cpp ****
74:../main.cpp **** asm volatile (
75:../main.cpp **** "ldr r4, .List \n\r"
76:../main.cpp **** ".Label1: \n\t"
77:../main.cpp **** "str %[H],[r4,#20] \n\r"
78:../main.cpp **** "str %[L],[r4,#20] \n\r"
79:../main.cpp **** "b .Label1 \n\r"
80:../main.cpp **** ".List: \n\r"
81:../main.cpp **** ".word 0x40020000 \n\r"
82:../main.cpp **** :
83:../main.cpp **** :[H] "r" (high),
84:../main.cpp **** [L] "r" (low)
85:../main.cpp **** : "r4"
86:../main.cpp **** );
52 .loc 1 86 0
53 0028 0020 movs r0, #0
54 002a 0123 movs r3, #1
55 .syntax unified
56 @ 86 "../main.cpp" 1
57 002c 014C ldr r4, .List
58
59 002e 6361 .Label1:
60 0030 6061 str r3,[r4,#20]
61 0032 FCE7
62 str r0,[r4,#20]
63 0034 00000240
64 b .Label1
66 .List:
67
68 .word 0x40020000
69 0038 5DF8044B |
意図通りに、最速でGPIOAに1.0を書き込むプログラムになってると思います。
オシロで出力を見てみます。
![](https://blogimg.goo.ne.jp/user_image/7c/66/26af05f0104077d94dca3ef2e4ac0302.png)
早すぎるせいか、1Vぐらいしか出てませんね…。約36MHz出てます。
1ループ:180÷36=5サイクルでしょうか?strが1サイクルとすれば、ジャンプが3サイクル…?(近距離の条件なし相対ジャンプで3サイクルも食うかしら…?)
1を書くstrと0を書くstrの間にnopを3つ入れてみました。
![](https://blogimg.goo.ne.jp/user_image/24/89/d9bbf2275be354249fc5d19741410085.png)
今度は25~26MHz出てます。180÷7=25.7MHzで、7サイクルになったのかしら…?
上のnop無しの「str:1-str:1-ジャンプ:3」と計算が合いませんね…。
(cortex-M4の命令実行サイクルの一覧表を探したのですが、見つけきれませんでした…。)
ちょっとすっきりしませんが、とりあえずこの環境(e2studio=eclipse)でインラインアセンブルを使って、最速でのポートON/OFFが出来た、という事で、今回は終了としますw。