SysTickを使ったSTM32のUSB仮想UARTの速度測定
転送速度を測ろうとはじめたタイマーの設定に難航する(5/22/09)
RTC(リアルタイムクロック)のプロジェクトの前に、もう少し仮想UARTモニターをまともなものにしようと色々考えている。週2日出ている事務所の往復がこういうロジックをまとめる格好の時間だ。
今のプログラムの送信データ量はパケット1個あたり、わずか1バイトだ。USBの送信ポーリング周期を1msとすると、ボーレートは8kbpsしかいかない。今はキーボードからテストメッセージを出しているだけだからこれでも良いが、SDカードあたりのテストでは、少々遅すぎる気がする。
ダブルバッファーにすれば、理論上では、エンドポイントのパケットバッファーが64バイトあるので、64×8×1000、512kbpsまでスピードを速めることが出来る。これならUARTの最速460kbpsでも楽々だ。やりかたはそう難しくない。バッファーを2つ作って、セマフォーのようなフラグを立て、空いている方へデータを移動させればよい。バッファーが満杯になれば、そこが0になるのを待つだけである。ついでにインターバルタイマーを入れて経過時間を表示するようにすれば改良の成果が良く分かるし、タイマーの勉強にもなる。これは面白そうだ。当面の目標をこれに決めた。
そこで、STM32のタイマーを詳しく調べ始めたが、これがAVRと違って設定がとんでもなく大変である。高機能タイマーでなく、汎用のタイマーでも設定するレジスターの数は、数えてみたら何と20個もある。こちらはPWMのような複雑なことをやるわけでなく単純なインタバルタイマーが欲しいだけなのだが、これでは、まともに設定するまでどれだけ時間がかかることやら見当がつかない。どこかの応用例を見て組んだほうが早そうだ。
タイマーなら、最初のテストに使った雑誌(DWM2008/6)のGNUサンプルソースにもあり、10ms単位で時間が測れる。しかし、こちらはSysTickを使ったタイマーで、ここでの設定はレジスターを直打ちで定義しているため、現在のソースに簡単には組み込めない。SystickはCPUの根幹部分であり下手に設定を変えると全く動かなくなる恐れがある。これも容易な作業ではない。
どちらも壁が高いので、とりあえずは今動いている仮想UARTモニターから、不要なコードを取ってスリムにし、追加開発を楽にしようとした。ところが、これも結構難しいのである。もともとこのコード(蛙がピョン)はSTマイクロのVCP(Virtual COM Port)デモプログラムをベースにしている。ADCやDMAはそう問題なく削除できたが、UARTの関数群がはずせない。仮想UARTと言っても、外にだすわけではない。モニターの内部でUSBとの仮想UARTの入出力をするだけだから、外部のUARTは全く不要なのだが、これが微妙にUSBルーチンとからんで、取りすぎるとコンパイルエラーになったり、暴走したりする。
sprintfの不具合解消。リンカースクリプトの不整合だった(5/24/09)
少しづつコードは減ってきたのだが、そのうち、仮想UARTモニターが途中でリセットがかかるようになってしまった。最初は、データを出力した直後一回リセットされるだけで、そのあとは問題なかったのだが、コードを減らしていくうち、遂に出力も出さずハングアップしてしまう。
どうも、以前AVRで経験したスタックがデータをつぶしていく不具合に似ている。プログラムサイズをかえるとトラブルが変化するというのは、こういうときの典型的な現象だ。sprintfの実行で暴走するようなので、sprintfを再びコメントアウトするとOKとなった。やっぱりここが原因だ。うーむ、ヒープ開始アドレスがうまく伝わっていない感じである(ハングとは関係ないがsprintfをとるとフラッシュサイズは37 KBから11KBまで劇的に下がった。そうかGNUではこのへんがタコなのだなと納得)。
良くわからないがマップファイルを調べる。ARMのマップファイルはAVRのnmコマンドと同じくらいの情報量があり(恐らく同じコマンド)、何とか書いてあることがわかる。ヒープアドレスを示す_endも載っている。あれえ、このアドレスがmain.cのSRAM変数の最初をさしている。このままではもろにかぶるはずだ。おかしい。
暴走の原因を確認するため、とりあえずはヒープのスタートアドレスをmain.cの変数が終わったところのアドレスに、マップファイルを参考に決め打ちし、sprintfを入れてテストしてみた。おおお、うまくいった。リセットもせず快調にsprintfが動く。
やっぱり、データのかぶりだった。しかし、何故、_endが正しくエリアの最後を指さないのだろう。今まで敬遠していたリンカースクリプトの勉強が必要になってきた。ウェブで調べ始める。このあたりはコンピューターシステムの専門分野でも一番奥の技術に相当ししかも機種依存が大きく、メーカーの技術者でもここに詳しい人は滅多にいなかった。
昔はほとんど情報が外に出ない部分だったが、さすがはGNUである。沢山情報がある。ありがたい時代になったものだ。おおよその文法がわかったので、ウェブから拝借してきたリンカースクリプトファイル、memory.defを調べる。
もういちど、マップファイルにもどって中身を見てみる。おや、COMMONというセクションがあり、これらがすべて.bssのあとに割り付けられている。これはmain.cの変数だ。
おやおや、COMMONは、リンカースクリプトの.bssの範囲に入れないと全部外に出されてしまう。なんだやり方が書いてあるではないか。オブジェクトファイルかリンカーオプションの宣言の不一致だ。COMMONを.bssの中に入れなおす。
.bss : {
. = ALIGN(4);
_sbss = .;
*(.bss)
*(COMMON) /* ここを追加する 5/24/09 */
. = ALIGN(4);
_ebss = .;
} > RAM
コンパイルする。やった。きれいに最終アドレスが_endに入った(sprintfで_endを表示させて確認)。やっとリンカーの仕組みもわかってきた。いやあ、組み込み系は奥が深い。30年前にやっていた大型機のオンラインシステムのシステム生成(シスジェンと言っていた)の経験が役に立つとは。この経験があったから、リンカースクリプトもそれほど恐怖感なく眺められたが、経験のない人にとっては、恐らく記号の密林に立ちすくむばかりだろう。ここはやはりかなり特殊な世界だ。
いつになく気分が良い。今度のトラブルシューティングは、昼なお暗いジャングルか真っ暗闇の洞窟のアドベンチャーゲームで、何か動いたのであてずっぽうに撃ったライフルの弾が見事に怪獣に命中したのと同じような気分だが、それでも全く偶然ではない。仮説にもとづいて手順を考え、その結果トラブルが解消したときは、どんなささいなことでも格別良い気分である。嬉しいことにもうひとつの不具合も解消した。
NOP_Process( )がないとハングする原因は、「ねむい」さんからコメントがあり、コンパイラーの最適化のやりすぎではとの指摘があった。これまでテストできなかったが、ついでなので指摘どおり、外部参照宣言(extern)にもvolatileをつけてコンパイルしてみた。結果はその通りでどちらのNOPも必要なく、プログラムは問題なく動いた。変数宣言の方にはvolatileをつけていたが、externにまで必要とは思わなかった。
1バイト送信で57kbpsも出ていることがわかる(5/27/09)
タイマーの勉強はなかなか進まない。8ビットのAVRのときは楽だった。単純な8ビットタイマーのオーバーフローから始めて自然に複雑なコンペアレジスターを使ったPWM制御まで理解できたが、いきなり10個以上のレジスターを前にして、さあ設定しろと言われても簡単にできるわけがない。32ビットプロセッサーは明らかに初心者向けではない。付録基板のついた雑誌には季節的に良く初心者向け特集などと銘打ってあるが、これは明らかに誤りだ。
本当に初心者向けに基板を付録にするなら8ビットプロセッサーでないと無理だと思う。もっともこれでは基板に魅力がなくなり売れないのだろうけれど、32ビットプロセッサーの付録基板で、向学心に燃えた初心者が、途方もない壁にはばまれてどれだけたくさん挫折しているかと思うと心が痛む。ステップを踏みさえすれば、そう難しいものではないのに。
それにしても、良いお手本が見つからない。見つかってもレジスターを直打ちで設定しているソースコードなので、STマイクロのデモソースベースのコードで動くか自信がない。SysTickを使ったタイマーの方が簡単そうなので、STマイクロのハードマニュアルや、ライブラリのSystick.cのソースを調べるが、ソースはいつもの長ったらしい変数の山で全くイメージがつかめない。マニュアルはOSのTick割り込みを意識した書きかたでこれも良くわからない。
それでも収穫はあった。SysTickはシステムクロックとは独立したリソースで、Tickをだすタイミングを設定するレジスターは根幹部分のクロックリソースとは独立しているということである。そうなると、このあいだのGNUサンプルのSysTickを使ったタイマーが使えそうだ。
定義を仔細にみていくと、SysTick関係の定義部分だけ持ち出せば、他に影響がなさそうだ。始め関係があると思ったのは、システムクロックの変数が大量に使われていたためだが、よく読むと、これは単に参照しているだけで本体とは関係ないことがわかった。
まあ、何かがこわれるわけではない。SysTickハンドラーの割り込み先は、デモソースの中に既に定義済みだ。ここにカウンターを入れて、GNUサンプルの定義部分を慎重にとりだし、適当なテキスト表示の前後にタイマーステートメントをはさむ。
動かしてみた。最初は0としか表示されなかったが、設定を変えると、1という数字が表示された。おお、動いている。少しづつ設定を調整して、それらしい時間がでるようになった。sprintfを直しておいて良かった。プロンプトの前にmsで時間を表示するモニターが動き始めた。リターンキーを打つ間隔がこれで測れる。
雑誌では10msのTickということで、定義を調整し(それらしい定数を10倍した)、1msにしたが、どうも少し早い。ストップウォッチで時間を測る。1秒で、1500位のTickだ。さらに調整して1msのTickにする。いよいよ転送速度の測定だ。
おやあ、想定した8kbpsよりもっと早い転送速度だ。56kbpsは出ている。何度か、計算式を確かめる。間違いない。USBの外への転送ポーリングは1msだが、中はもっと早いタイミングなようだ。少なくとも8倍近く早い。そうか、STマイクロのVCPデモプログラムがV3でも改善されていないのはこのへんまでは破綻をしないためだ。これは今度のダブルバッファーの実装は楽しみになってきた。115kbpsや、230kbpsでも動くVCPは喜ばれるはずだ。
| 固定リンク
「ARM」カテゴリの記事
- 心電計プロジェクト:スケールが出ると心電計らしくなる(2015.01.08)
- 心電計プロジェクト:TFT液晶に念願の心電波形が出た(2014.12.18)
- 心電計プロジェクト:STM32F103の心電波形表示で悪戦苦闘(2014.12.03)
- 心電計プロジェクト:CooCoxでARMの表示系ソフトを開発する(2014.10.16)
- 心電計プロジェクト:表示部のARM基板の開発環境を一新する(2014.09.19)
コメント