koheiのおもちゃ修理記録

宇部おもちゃ病院 毎月第2土曜日 13:00~16:00
宇部新天町の西の端、市民活動センターで開院してます。

10/24 Nucleoボードをちょっと触ってみる~最速でGPIOをON/OFF

2020-10-24 | PIC・電子工作
買ってしばらく放置してたNucleoのSTM32F446REボード、ちょっとやりたい事があって使ってみることにしました。

前回(5/31 マイコンボードいっぱい買った①~Nucleoボード動作確認)の時の結論では、
「とりあえずオンラインmbedの方がarduinoライクに簡単にプログラムが書けそうだけど、もっと詳細な設定をしたければ、cubeMXで設定作ってmbedに必要な部分だけコピペするか、「cubeMX+SW4STM32」で作った方がいい」
となったのですが、mbedのオンラインコンパイラーは、どうもできる事が限られてる気がする…。
アセンブルリストを確認したいのだけど、どうしても出し方が分からない…。

だいぶ探した後、「エクスポートして別のオフライン環境で開発する」という作戦があるとの事。見てみると、

e2studioがある!SW4STM32もある!
e2studioはルネサスのRX621ボードの為にインストールしてちょっと使ったので使いやすい。SW4STM32は、一応入れたけど、まだあまり使ってないw。

エクスポートして開いてみたけど、どちら(e2studio/SW4STM32)もprintfがエラーになってビルドできない。printfを全部コメントアウトしても’undefined reference to _wrap_vsnprintf’とやらのエラーが出て、どうしてもビルドできない…。
いろいろキーワードで検索してもなかなか解決せず、最終的にいつも親切なルネサスのフォーラムでヒントを見つけて、e2studioで何とかビルドできて、アセンブルリストも見れるようになりました。
(具体的には、プロジェクトのルートにあるファイル’.cproject’の中から、エラーの出る_wrap_??printfを含む行を削除していったら、ビルド通るようになりました。)
英語にはそこそこ自信があるのですが、e2studioはフル日本語で、やっぱり助かる~ww。

プログラムは前回の「クロックを設定して、クロックの各設定値をprintfで出力した後、Lチカする」というプログラムですが、「mbedで作ったプログラムはサイズがデカい!」という情報だったので、プログラムサイズ見てみます。

やはり、クロック・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を操作し始める感じです。)

まず、先頭の番地を指定します。

次に、各設定のoffsetに従って、代入します。

元のプログラムでは、例えば「GPIOA->MODER |= 0x5555;」で1行であたかも即値代入ですが、or取ってる事もあって1命令では済んでませんが…、orが2つに分かれてます…。
なんででしょう?データシートを調べます。

「フレキシブル第2オペランドについては59ページを見てね」との事w。

(日本語のデータシートがゲットできて助かった~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を書き込むプログラムになってると思います。

オシロで出力を見てみます。

早すぎるせいか、1Vぐらいしか出てませんね…。約36MHz出てます。
1ループ:180÷36=5サイクルでしょうか?strが1サイクルとすれば、ジャンプが3サイクル…?(近距離の条件なし相対ジャンプで3サイクルも食うかしら…?)

1を書くstrと0を書くstrの間にnopを3つ入れてみました。

今度は25~26MHz出てます。180÷7=25.7MHzで、7サイクルになったのかしら…?
上のnop無しの「str:1-str:1-ジャンプ:3」と計算が合いませんね…。
(cortex-M4の命令実行サイクルの一覧表を探したのですが、見つけきれませんでした…。)

ちょっとすっきりしませんが、とりあえずこの環境(e2studio=eclipse)でインラインアセンブルを使って、最速でのポートON/OFFが出来た、という事で、今回は終了としますw。
コメント    この記事についてブログを書く
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする
« 10/18 プラレール修理4件 | トップ | 10/31 我が家のプリモ... »

コメントを投稿