2016年7月30日土曜日

インディペンデンス(independence class LCS)のウイング

話は後だ、まずはこの写真を。



File:USS Independence (LCS-2) and USS Coronado (LCS-4) underway in April 2014.JPG

手前にいるのがLCS-4 インディペンデンス級2番艦コロナド、奥にいるのがLCS-2 インディペンデンス級1番艦インディペンデンスになる。
よく見ると、LCS-2の方には艦橋にウイングがあり、LCS-4の方には無い事がわかる。

これはなぜ?
一番簡単な理由を考えると「設計時にはウイングが必要だとされたが、一番艦で試験したところ必要ではないとわかった。そのため以降の艦には実装しない」というもの。これが一番簡単だし、そうであればありがたい。
でも違う。



File:USS Independence demonstrates its maneuvering capabilities.jpg

この写真はLCS-2だが、ウイングは無い。
その理由は何か。「先の試験によりウイングは不要と判断されたので、ジャマだしステルス性悪くなるから取っ払った」ということかもしれない。
でも違う。

1枚目の写真は2014年に撮影されたもの。対して2枚目の写真は2013年に撮影されたもの。つまりインディペンデンスのウイングは後から追加されたものだとわかる。


最近ではLCS-14 Manchesterが3ヶ月ほど前に造船所から出てきた。

映像を見る限りではウイングは装着されていない。


しかし、先日RIMPAC 2016でLCS-4がハープーンミサイルを発射した際の写真を見ると、ウイングを装着しているように見える。


まとめると
1) LCS-2/4建造当時はウイングは装着していない
2) LCS-2/4は後からウイングを追加した
3) LCS-14は(少なくとも造船所から出る時点では)ウイングを装着していない

ということになる。


もうちょっと詳しく調べたいけど、今日は眠いのでここまで。

インディペンデンス級なんて全然好きでもないのになぜここまで調べてるかというと、某アニメ関係で、あっさりやられたあきづき型はグッズ化されてるのに、あれだけ活躍したインデペンデンス級がグッズ化されないから。商標とか面倒なんだろうなぁとは思いつつ、自分で作れるかなーと大きさとかを調べ始めたのが始まり。
作中ではインデペンデンス級は数隻出てくるが、1隻だけ確認した限りではウイングは装着されていなかった。ということでウイングは無視したほうが楽なのだが。ウイングあっても宇宙人の砲撃であっさりやられちゃうだけだろうし。

2016年7月28日木曜日

LCSからハープーン(あと某アニメの設定妄想)

最近はトレンドを追ってなくて流行に乗り遅れてて知らないことばかりなのですが。

某アニメでは主にWWII時代の船が主役ですが、インデペンデンス級が大量に出てきたので、まぁ満足です。全体的に評価すれば大満足ですが。


先日、RIMPACでインデペンデンス級LCSからハープーンミサイルが発射されたようです。

USS Coronado (LCS 4) launches harpoon missile during RIMPAC USS Coronado (LCS 4) launches harpoon missile during RIMPAC

艦砲の方から艦橋向きに撮った写真です。ハープーンBlock 1Cだそうです。



作中のインデペンデンス級の前部ミッションパッケージ搭載部は未搭載のままですが、作画を見る限りは前部からVLAを発射しているので、ここにはMk41VLSが搭載されていると思われます。VLAを発射するにはTactical-LengthのVLSが必要で、1セルあたりVLAを1発搭載することが可能です。
VLAを大量に撃っているのは、SM-2やESSMを搭載する必要が無いという設定が効いているのでしょう。飛行船はありますが、超音速で突っ込んできたりはしないので艦砲で対処していると思います。ESSMやRAM等の短距離SAMでも、その途中には空対空ミサイルの発展が必要ですから、おそらく存在していないでしょう(ESSMはスパローミサイル(AAM)からシースパローミサイル(SAM)を経て、RAMはスティンガーミサイル(SAM)やサイドワインダーミサイル(AAM)を組み合わせて)。

SeaRAMが乗っている気がしますが、気にしてはいけません。「SeaRAMのようなもの」は、おそらくファランクスCIWSでしょう。これはM61バルカンが必要ですが、逆に言えばそれだけです。M61の元になるガトリング銃自体は1860年頃に開発されている物です。ただしM61が開発されるには航空機の高速化が必要ですから、少し微妙な気がします。ただ、艦砲射撃や対艦ミサイルが発展した場合、機関銃が発展して迎撃することになり、機関銃の限界が見えてくれば誰かがバルカンを使うように言い出すでしょうから、航空機がなくともファランクスCIWS自体は発生可能だと思います。


で、最初の話に戻りますが、事態が発生するのが作中4月7日です。終盤のLCSと戦艦が戦闘する(そしてあっさりやられる)のが5月5日です。つまりその間は4週間あります。これだけ時間があれば魚雷で破損したLCSを曳航してくることは可能かもしれません。修理まで含めて間に合うかは微妙なところですけども。
(ちなみに、史実上で破損した船舶を修理した際には、輸送に2ヶ月、修理に3年ほどを要しています。ただし輸送は大圏ルートでも1万3千kmあるところを遠回りして輸送していますから、1000km程度の輸送であれば数分の1未満になると思います。)
そしてドックで修理を受けていたのならば、その間にハープーンを発射するパッケージを輸送・搭載・インテグレーションすることは可能でしょう。
ということで、「作中にハープーン(とそのミッションパッケージ)があればもっと楽だったのになぁ」という事。
もっとも、インデペンデンスにはヘルファイアミサイルも搭載されており、LCS全艦に搭載されているとすればこれを使えばもっと楽な可能性もあります。ただし、ヘルファイアは精密機器の塊ですから、動作異常を起こす可能性は大いにありえます。じゃぁ、ハープーンもダメかなぁ。


魚雷は異常動作をしている様子はありませんし、作中の設定「海水で解決可能」により、水中の魚雷は問題ないと思われます。
更に言えば魚雷でなくとも、水中であれば問題ないということであれば、数人乗りの小型艇で接近するより、SDVで隠密に接近すればよかったのかもしれません。まぁ、近くに着弾して弾頭が起爆したら嫌なことになりますけど。

M61バルカンの弾数

M61の弾数が知りたくなったのでちょっと調べてみた。数字はwikipediaから拾ってきた。

F-4 639
F-14 675
F-15 940
F-15E 450-512or510
F-16 511
F-22 480
F/A-18 570
F/A-18E 400
ファランクスBlk0 989
ファランクスBlk1 1550

F-15Eの弾数は、ja.wikipediaでは450-512と書いてあり、en.wikipediaでは510と書かれていた。まぁどっちにしろ500発前後くらい。
オマケでファランクスも。個人的にC-RAMの弾数が知りたかったけど、これはざっと調べた限りでは見つからなかった。

CIWSを除くと、F-15の弾数が飛び抜けて多い。F/A-18Eの倍以上ある。
時代としては機銃を廃して苦戦したF-4にM61を追加した直後あたりからF-15の開発が始まったこともあり、機銃を重視した設計が行われたんだと思う。
もっとも、その数年後から開発が始まったF-16は半分程度しか搭載していないため、M61熱もすぐ覚めたんだと思う。LWFという特性も有るんだろうけど。
F-15の20年ほど後に開発されたF-22でも500発弱程度と、F-16と同程度に収まっている。コイツは先制攻撃を主眼においているので、M61の優先度はそれほど高くないはずだが。
F-22から遅れること約10年程のF-35ではご存知の通り、A,B,Cの内B,Cは機銃を固定では搭載せず、機内に搭載するA型でも180発と大幅に減らされている(F-35の機銃についてはM61ではなく、口径も違うため表には掲載していない)。



ロボットアニメで武器をもたせるなら、僕としてはファランクスを強く推奨する。
主武装としては5インチ砲クラスが欲しいかもしれないが、少なくとも常設の副武装としてはファランクスは理想的だと思う。牽制にも使えるし、軽装甲目標なら高価な大口径を使うまでもなく破壊できる。それに敵にミサイルを撃たれても迎撃することができる。

主武装として5インチクラスを使うならMk45の改造になるのかな。海軍と共通になるので、揚陸艦から強襲するなら便利だと思う(機体を海軍所属にしないとダメなのが欠点か)。それに今なら5インチサイズの誘導弾もあるから、プログラムを書き換えればロボット側のレーダーから指令誘導が可能だろう。
陸軍と行動をともにするなら、155mm砲を使うのもありかもしれない。
そういえば、某アニメでは120mmを搭載しているらしい。これは現代の戦車砲互換なのかな。粘着榴弾とか使われるとロボット的には辛いかも。高機動が可能な相手にはAPFSDSのような高速弾が有効かもしれない。


ロボット同士が戦う場合、物理で殴るとか刃物で斬りつけるとかを除くと、ある程度離れた距離になるはず。だがその距離がどれくらいになるかは僕には想像できない。

ランダムに動きまわる相手に砲弾を命中させるなら500mくらいが限界かもしれない。それでも弾着まで0.5秒くらいあるから、人間の反応速度以上の時間が掛かるし、高周波レーダーや赤外線で相手をトラッキングしていれば砲弾の発射を検出して自動で回避するくらいは可能だろう。最終的にはAPFSDSを100m前後で撃ちあうようになるのかもしれない。
逆に、もっと遠くになる可能性もある。127mmや155mmの砲であれば射程はかなりの距離になるし、ロケットアシストや誘導を行えば更に伸ばすことが可能。ここまで離れるとレーダーやIRで発射を検知するのは大変だろうから、稜線に隠れて山の向こうの相手に静かに撃ちこむことは可能かもしれない。もし可能だったとしても前述のCIWSを超えなければいけないから、相手1機に対して4機程度でMRSIを行う必要があるだろうが。

もしもロボットが出てきた場合、それに対抗できるのはB52のような大型航空機になる可能性もある。B52にファランクスを搭載し、対空ミサイルや砲弾を迎撃しつつ爆撃を行ったりという方法。もしも大国同士でなりふり構わず戦争になればこのような戦い方があるかも。そうなると航空優勢を確保したほうが有利になり、結局現代戦の拡大になってしまうのかもしれない。

2016年7月26日火曜日

今日のISS

今日も懲りずにISSを撮ってきた。昨日とくらべて1時間ほど早い時間帯。まだかなり暗かった。



今回は最接近で560kmくらい、仰角45度くらいだった。そのため若干解像度が悪い。けど望遠鏡で撮るならこれくらいの仰角が楽でいい。


ファインダーを覗くとISSなんてただの点でしかないから、合焦してるかなんて全然わからない。しょうがないので左手でフォーカスを動かしながら右手でシャッターを押してるけど、ほとんどがピンぼけの画像となる。この辺りはどうにかしたい。


例えばα7sなんかであれば4KでHDMIに出せるから、4Kを受けれるキャプチャボードを使ってISSの部分を拡大すれば合焦してるかを判断できる。それを元にしてフォーカスを動かすモータを制御するとかすればいい。ただこれをやるには追尾と別の人間が必要になる。
電動雲台を作ってそれに載せて、HDMIのキャプチャから点を追尾するような動作をさせれば勝手にISSを追尾するので、撮影する人間はフォーカスに専念できる。
ということでサーボモータとかで電動雲台を作りたい。前に似たような物のソフトウェアを書いたことはあるので、多分作れると思うのだが。910mmの光学系なら視野広いからそんなに精密じゃなくて良いはずだし。

一番大きな問題は、4Kを受けれるキャプチャ&それを処理する画像処理システムかもしれない。他の小さな問題は、確かA80Mfにα7sをつけると画角が足りないので周囲が写らない。その問題もあって、マウントアダプタはEも買ったはずだけど使っていない。あとK-5はAPS-Cで4.9kに対し、α7sはフルサイズで4.2kと、K-5よりも遥かに解像度が低い。なのでISSを撮るのには向かないと思う。(あと、現状Eマウントレンズは1本しか持ってないからいっそレンズ交換せずに埃とか入れないというデジイチに有るまじき運用をしているのもある。)

他の方法としては、WiFi内蔵SDカードを使用するという手もある。これならK-5の高解像度を活かせるが、撮影間隔が広いのでフォーカス調整が大変、タイムラグが大きい、などの問題が考えられる。


とまぁいろいろ妄想はできるが、電動雲台作ってるお金あるなら焦点距離長い望遠鏡買えよ、という話になってしまう。ま、どちらにしてもお金足りませんというオチになるわけだが。

2016年7月25日月曜日

ISS

ISSを撮ってきた。



あいも変わらず機材はK-5にA80Mfの組み合わせ。今回はちょっとISO感度を落としてみたのだが、暗すぎた。やっぱ1/500sec、ISO5000あたりが良いのかなー。

今はSpaceX CRS-9がドッキング中で、ISSの前方、地球側に停泊中らしい。地球側にいるわけだから、十分な解像度の望遠鏡があれば見えると思う。この写真ではそもそもどの向きで映ってるのかもわからないけど。

天体望遠鏡入門用フルセット4万円の鏡筒じゃこれくらいが限界かなー。ちゃんとISSの形が見えるだけで凄いといえばそうなのだけど。


数日前に満月だったので、3時過ぎに撮影に行った時は月が綺麗に見えていた。



満月でもなく、いい感じにクレーターが高コントラストで写ってる。
やはり天体望遠鏡は天体を見るのが一番綺麗に見えるのであろう。と言っても月以外の天体で見えるものといえばオプションを追加して太陽くらいな気がするなぁ。土星は輪が見えるらしいけど、まだ見たことがない。これは今後の課題か。


ISSの撮影もだんだん飽きてきた。この時期は虫が凄くて、強めの虫よけを塗ってても袖の隙間に入り込んで吸血していく。かなり嫌な季節。

2016年7月17日日曜日

クラシックカーツーリングラリー

昨日(16日)に赤平の植松電気でクラシックカーツーリングラリーのイベントでいろんなクラシックカーが集まると聞いて行ってきた。
といっても昨日は偶数月の第3土曜で、コズミックカレッジとかスポーツシューティングクラブの例会とかいろんなイベントにまとめて行ったのだが。

とりあえずクラシックカーの写真を紹介する。
僕は地べたを這いずりまわる乗り物の写真は1,2回しか撮ったことがなく、それも自衛隊の観閲式とかなので、ピカピカの車というのはあまり撮ったことがない。なのでほぼ素人が撮ってるというのを前提にお手柔らかに。

今回は駐車場にパイロンを並べてコースを作り、コース上に設置されたテープスイッチを踏む時間を測るという感じ。踏む時間は予め決めてあり、例えば90メートル間隔のスイッチを5秒で通過せよ(数字はうろ覚え)、という感じだった。この時間は早くても遅くても減点対象となる。なので基本的にすべての車両は同じ速度で走ることになる。ただドライバーとかナビゲーターによって早めに動いて計測点前で長めに一時停止とかいろいろとある。

一応クラシックカーのイベントなので懐かしい雰囲気の車が多い。懐かしいと行っても僕が生まれてすらいない頃の車だけど。

僕は車は全くわからないのでいつ頃の車とかはわからないのだけど、一例を上げると「BUGATTI 35A」という銘板の付いた車があった。これはwikipediaによると1925年に139台が作られた車らしい。

すべての車は撮り切れなかったので、とりあえず撮れた車だけ一覧。たぶん走行順に並んでる。











































###



最後にCAMUIの燃焼があって、車関係のイベントはこれで終了。
CAMUIを見るのは久しぶりで、想像以上に音が大きかった。いままで赤平で見た燃焼って最近のはサイレンサーが付いてたので、結構静かなイメージだったんだけど、サイレンサーは冬に別の場所に移動されていまは何もない。CAMUI自体も気体酸素を使う、gasCAMUIに変わっていた。

2016年7月15日金曜日

EAN(JAN)コード

EANコードをデコードする方法について。あんまり詳しく調べてなくて、ノリと勢いだけで書いてるので間違ってたらごめんなさい。

EAN/JAN/UPC等ではいろいろな規格がありますが、(調べるのが)面倒なので12桁/13桁に限って説明しています。


画像1


画像2


EANコードでは白(高反射率)の背景に黒(低反射率)の棒を書いてバーコードとしている。低反射率の背景に高反射率の棒でもいいみたいだけど、とりあえずコントラストが充分に確保できればよろしい。
棒(Bar)の幅は4種類あり、一番細い棒をナローバー(Narrow Bar)と呼ぶ。ナローバーの幅は標準が0.33mmで、0.8倍から2.0倍までが許されている。4種類はナローバーに対して1倍、2倍、3倍、4倍の比率を持っている。
数字1桁を表すにはナローバー6本分の空間を使用し、隣との間にはナローバー1本分の白が追加される。他にガードバー(レフトガードバー、ライトガードバー)とセンターバーがあり、これは黒1本、白1本、黒1本の繰り返しパターンとなる。

画像1は幅の組み合わせを示しているが、ナローバーn本分という書き方になっている。例えば左Oddの0は白2本、黒2本、白1本、黒1本、白0本となっているが、これは6本分のスペースに、左から白2本、黒2本、白1本、黒1本というパターンを示している(画像2のOdd0パターン参照)。

数字の表し方は3種類あり、左側が奇数(Odd)と偶数(Even)、それと右側が偶数のみで計3種類となる。

EANコードではバーを30本使用し、そのうち6本はGB/CBとなり、残りの24本を数字として使用する。数字1桁当たり2本を使用するので、24本では12桁となる。つまりEAN(正確には元になったUPC)コードでは12桁しか表すことができない。
ではEANコードではどうやって1桁増やしているかというと、左側の数字6桁を表すコードをOdd/Evenの組み合わせで表記し、その組み合わせから1桁目を推定している。
ISBNで使われる9はOEEOEOの組み合わせになり、JANコードで使われる4はOEOOEEとなる。棒を正確に読み取れていればOdd/Evenを一意に判別できるから、先頭6桁を読んだ時点で最上位1桁を求めることができる。

今のところ右側はEvenのみを使用しているらしく、テーブルは1つしか無い。

チェックデジットの計算だが、これは奇数桁が1倍、偶数桁が3倍の重みを持ち、12桁を加算した下位1桁を10から引いた数がチェックデジットとなる。

#追記:2016/07/15 14:45

手元にあった海外製のパッケージ(Surefireの電池)を読んでみた。これはアメリカ製で、UPCの12桁表記となっている。読み方はEANと同様だが、左側は奇数のみを使用している。チェックデジットの重みは奇数と偶数が逆になっている。
EANとUPCを同じように計算したい場合は、チェックデジットの重みを下位から奇数桁は1倍、のようにすると良いはず。この時の下位にはチェックデジットの桁は含まない。

#追記ここまで



Webカメラから画像認識でやる場合、手っ取り早い方法は画像の画像の幅x高さ10ピクセルくらいの帯を切り出して、それを二値化して幅を計測、という感じだと思う。この方法だと簡単に実装できるが、外乱光に弱かったり、向きを正しく合わせないと読み取れないという問題が有る。
せっかくカメラで読み取るなら様々な向きでも読めたほうが便利なので、そのあたりは画像処理とかで頑張るしか無いかも。
カメラで読む場合、ナローバーの幅が10ピクセル以上あれば処理しやすいはず。バーコードの端から端までが800ピクセルくらいになる。
複数の商品を並べて一気に読み込む場合、解像度は数kピクセル四方から数十kピクセルくらい必要だと思う。Webカメラでは非現実的な気がする。HDMIで4kを受けれるキャプチャカードを使うとかそういう方向になるのかな。


自分でバーコードを読んだところで、実用性は皆無だが、暇で暇でしょうがないという人は、自前でバーコードを読む処理を書いてみてはいかが?
動作確認のサンプルは身の回りにたくさんあるので、そのあたりの心配をしなくていいというのは気楽かも。例えばペットボトルやお菓子にはJANコードが書いてあるし、本の裏にはISBNが書いてある。

2016年7月14日木曜日

OpenCvSharpでWebカメラをキャプチャしてFormに表示

マイコンに飽きてきたというか、ハードを考えるのに飽きてきたのでまたPCに戻ってきた。
とりあえずOpenCvSharpで遊んでる。で、タイトルの件。

特に難しいことはない

Thread t;
readonly int CameraIndex;

public Form1()
{
    InitializeComponent();

    CameraIndex = 0;

    t = new Thread(new ThreadStart(ImageLoad));
    t.IsBackground = true;
    t.Start();
}

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
    t.Abort();
}

private void ImageLoad()
{
    using (CvCapture cap = Cv.CreateCameraCapture(CameraIndex))
    {
        while (true)
        {
            using (IplImage img = cap.QueryFrame())
            {
                Bitmap bmp = BitmapConverter.ToBitmap(img);

                if (pictureBox1.Image != null)
                {
                    pictureBox1.Image.Dispose();
                }

                pictureBox1.Image = bmp;
            }
        }
    }
}

ロード時にバックグラウンドスレッドを作ってFormClosedの時に止める。バックグラウンドスレッドの中ではキャプチャを作って無限ループで読み込みと表示を行う。
OpenCvのWindowを使うときにはCv.WaitKeyが必要だが、Formに表示するなら不要。QueryFrameで適切にウエイトが入る。というかカメラが1コマ撮影しないとスレッドに処理が返らない。カメラの性能に左右されるが、数fps-数十fpsくらいで安定する。これはカメラや帯域以外にも、光量にも依存する。つまり周りが暗いとカメラはシャッタースピードを遅くする必要があるから、FPSが稼げなくなる。

ただしCaptureがカメラではなく動画ファイルとかで、リアルタイムに表示する必要があるならcap.Fpsを使って適切なディレイを設定すること。ただThreadのSleepとかはあまり正確ではないので、例えば1時間の動画ファイルをちょうど1時間で表示しなければいけない場合などは工夫が必要。

OpenCvSharpの動作確認とかで途中の画像を出力したい場合もBitmapConverter.ToBitmapでC#が扱える画像に変換してPictureBoxに渡してやればOK。

2016年7月10日日曜日

早速日和る(センサの配置)

前回のエントリでセンサの配置は正八面体に決定した、はずだった。が、あっという間に変更となった。
1つは45度の送信と45度の受信では利得が全く足りないこと。意外と指向性が尖いらしい。
もう1つは35cmほど離すとTx-Txでは受信できないため。

ということで今のところ最有力な配置が以下の図。



基本的にTx-Txを1対1で双方向型を3セット配置している。ただしセンサを横にオフセットしてあるので中心には音は通らない。なので中心にアルミパイプを1本入れて支柱としている。
丸パイプだと設計が楽(1つのアームを6個並べるだけ)だけど、回転防止の意味では角パイプを使ったほうがよろしいと思う。それからアームの断面が角になっているが、気流を考えると丸型のほうが良いと思う。

センサ自体が開放型なので、常設での使用は不可能。おそらく三脚の上に載せて必要なときに設置するという使い方になると思う。雨が降ったらビニール袋でもかぶせる方向で。

ムラタとかで防水のTx-Rx一体型センサがあるけど、あれは指向性が円錐ではなく、楕円のような形になっている。正八面体だと指向性がゆがんでるのはマズイけど、今回のように1対1で使うなら特に問題にはならない。このセンサを使えば屋外に常設することも可能かもしれないが、唯一の問題は入手性がとても悪いこと。RS等でも扱ってないらしい。


だいたいの形は見えてきたので、あとはアナログ回路を作るのが壁になってくる。だれか回路やってくれないかな。
基本になる回路はムラタのデータシートに書いてあるから、これを僕が扱いやすいように変更してみて、それを試してみて。

センサの配置



とりあえず、正八面体の頂点に配置してみた。フレームはタミヤのユニバーサルアームを使用して、センサの保持部だけ何か部品を考える。

正八面体の頂点に配置する利点は結構多くて、6個のセンサの内送信しない5個全てで受信することができる(かもしれない)、一番離れているセンサは正面に有る(利得が稼げる)、3軸に直行する軸があるので簡単なコードで動作確認できる(かも)、等。あと見た目に綺麗な気がするというのも有る。
それとSTM32F1のPWM出力は、リマップを使えば6ch出せるので、正八面体の頂点6個というのは都合がいい。

正八面体だと隣のセンサの角度が45度になるので、感度が悪いかもしれない。かといって正四面体だとセンサの数が半端だったり、計算がちょっと面倒だったり。


タミヤのロングユニバーサルアームを使う場合、隣のセンサまで240mmくらい、対角のセンサまで340mmくらい、になるらしい。隣のセンサはちょっと近い気がするけど、対角ではある程度離れてるので、ソコソコ分解能稼げるかも。


まずはアナログ部分をちゃんと確認しておかないとね。オペアンプとか、送受信を切り替える回路とか。あとPWMをちゃんと6ch出せるかも。

超音波センサ

手持ちの超音波センサは秋月で売ってる送受信セットで、商品名の通り、送信と受信が別になっている。ただ超音波センサは比較的大型のため、送受信を並べようとすると結構場所をとる。ということで1個で送受信できないかと試してみた。

まず送信部(A)と受信部(B)をオシロに接続する。それとは別に送信部(C)をマイコンに接続し、40kHzを出す。AとBはほぼおなじ場所・同じ向きに置いてあり、A-C、B-Cの距離はほぼ同じで、約20cmのところにおいてある。Cに入れるPWMは3.3VのPWMを逆位相で入れており、約7Vppくらい。受信した結果が以下の図。



赤が受信(B)に接続したプローブ、黄が送信(A)に接続したプローブ。赤は100mV/divで、黄は10mV/divとなっている。だいたい10倍弱くらい感度が違うらしい。
極めて近距離ではAとBの感度の差が縮むが、15cm程度になると10倍弱で安定する。

上で試したのはA-C、B-Cが真正面から向き合っている状態で、斜めに設置すると更に弱くなる。それでもAで10mVppくらいは取れる。
数百倍に増幅してADCに突っ込めば検出できそう。

1個で送受信できる超音波センサは市販品でも有って、やはり設置スペースやコストの面で有利なんだと思う。しかしムラタのデータシートを見る感じでは、アナログ回路がかなり高コストになりそう。15Vの正負電源を用意して、トランスでアイソレートしたり、オペアンプ4段で増幅したり。


とりあえず、超音波センサは1個で送受信に使えることがわかったので、設置スペース的に有利になった。あと3D造形で取り付け部品作る予定だけど、その部品も小さくて済むからかなり安くなりそう。


だんだんハードウェアよりになってきた。最初はSTM32F1にFreeRTOS走らせたいだけだった荷に、どうしてこうなった。

2016年7月9日土曜日

タイマのレジスタ

STM32F1のタイマ周りのデバッグでレジスタを読みたくなった。複数のタイマの、複数のレジスタにまたがってのデバッグなので、いちいちデバッグ用のコマンドを実装するのも面倒なので、レジスタのポインタを返す関数を作った。

volatile uint16_t* GetTimRegPtr(int idx, const char* regname) {
    TIM_TypeDef* TIM;
    
    switch (idx) {
    case 1: TIM = TIM1; break;
    case 2: TIM = TIM2; break;
    case 3: TIM = TIM3; break;
    case 4: TIM = TIM4; break;
    case 5: TIM = TIM5; break;
    case 8: TIM = TIM8; break;
    default: return(0);
    }
    
    if (!strcmp(regname, "CR1")) { return(&TIM->CR1); }
    if (!strcmp(regname, "CR2")) { return(&TIM->CR2); }
    if (!strcmp(regname, "SMCR")) { return(&TIM->SMCR); }
    if (!strcmp(regname, "DIER")) { return(&TIM->DIER); }
    if (!strcmp(regname, "SR")) { return(&TIM->SR); }
    if (!strcmp(regname, "EGR")) { return(&TIM->EGR); }
    if (!strcmp(regname, "CCMR1")) { return(&TIM->CCMR1); }
    if (!strcmp(regname, "CCMR2")) { return(&TIM->CCMR2); }
    if (!strcmp(regname, "CCER")) { return(&TIM->CCER); }
    if (!strcmp(regname, "CNT")) { return(&TIM->CNT); }
    if (!strcmp(regname, "PSC")) { return(&TIM->PSC); }
    if (!strcmp(regname, "ARR")) { return(&TIM->ARR); }
    if (!strcmp(regname, "RCR")) { return(&TIM->RCR); }
    if (!strcmp(regname, "CCR1")) { return(&TIM->CCR1); }
    if (!strcmp(regname, "CCR2")) { return(&TIM->CCR2); }
    if (!strcmp(regname, "CCR3")) { return(&TIM->CCR3); }
    if (!strcmp(regname, "CCR4")) { return(&TIM->CCR4); }
    if (!strcmp(regname, "BDTR")) { return(&TIM->BDTR); }
    if (!strcmp(regname, "DCR")) { return(&TIM->DCR); }
    if (!strcmp(regname, "DMAR")) { return(&TIM->DMAR); }

    return(0);
}

とりあえずSTBeeで使ってるのでTIMは1,2,3,4,5,8を使えるようにしている。TIMxはdefineで宣言されているので、ifdefでcaseを囲ってもいい。
引数はタイマの番号と、レジスタの名前を文字列で渡す。不正な番号orレジスタ名を渡した場合は0(ぬるぽ)を返す。それぞれが正常だった場合はvolatile uint16_t*を返す。
USARTから文字列を受け取って、TIMの番号とレジスタ名を読み込んで、それを引数に渡して、ポインタ内の値を表示する、みたいな機能を作ればレジスタの値を読み取ることができる。
ポインタに値を設定すればレジスタを変更できる。



PWMとADCを高精度にタイミング合わせて走らせたいけど、結構面倒っぽい。あとSTM32F1を72MHzで走らせてるとADCは1Mspsではサンプリングできず、およそ0.85Mspsくらいが上限っぽい。コアを56MHzで動かせば1Mspsで取れるらしいが。
それとADC1とADC2を交互にサンプリングする方法も謎。おそらく高速インターリーブモードでできるんじゃないかと思うが。これはトリガ1個でどんどん変換していくので、止める方法が不明。
まぁいろいろな方法がありそう。期限があるわけでもないのでゆっくりと調べつつ。頭を休めつつ。

オシロのUSBケーブル

僕が使ってるオシロはPDS5022Sという、STN液晶を搭載した、現行モデルのひとつ前くらいのモデル。STNは非常に見づらくて、正面から少しずれるとすぐ見えなくなる。現行モデルはTFTになってここが改善されたみたいね(すでにディスコンマークついてるけど)。

さて、このオシロはUSBでPCに接続することにより、BMP or BINのデータを抜き取ることができる。BMPはオシロの640x480液晶に表示している画像をそのまま取り出す機能。BINは波形データのバイナリデータ(ADC変換後のデータと、トリガとかスケールとか表示位置とか)を取り出す機能。ちなみにBINはフォーマットが解析されているので、自分で通信プロトコルのデコードを作って動作確認とかもできます。CANとか安いオシロでは解析できないからね。

で、このUSBケーブルだけど、結構ヘンな仕様で、オシロ側のコネクタはA型になっている。つまりA-Bの一般的なケーブルを使えない。
おそらくUSBプリンタなり、USB MSCなりを使えるようにしたかったのか、上位機種にはそれが搭載されていて、その筐体を流用したんだろうけども、このUSBケーブルを無くすととても困る。こんなケーブル使うのは他に無いだろうから、オシロに繋ぎっぱなしでも良いんだけど、とにかく無くしてしまったものはどうにかしないといけない。ということで作る。




作ると言っても見て分かる通りすごく簡単だが。
とりあえず手持ちのminiBコネクタを2個向かい合わせに接続した。今回はUSBの配線を引き出すのにピンヘッダを載せたコネクタを使ったが、最初からこれ用に作るならスルーホールを重ねてハンダを流し込んだほうが強度的によろしい。後は好みでシュリンクチューブとかで保護する。と言うか抜け防止に。
機械的強度を気にしないならminiBのコネクタを直接2個重ねてもいいかも。

2016年7月8日金曜日

TIM-DMA-DAC

STM32F1のDACから正弦波を出しました。





若干高調波がありますが、かなり綺麗な波形です。DACが2chあるので、時間方向に正弦波の強さを変えつつ、逆位相を出力したりできます。なのでAM変調波とかも出力できます。ま、100Hz当たりが限界で、送信出力も極めて弱いので普通のラジオ程度だと受信できないですけど。


TIM_TimeBaseInitTypeDef TIM_InitStructure;
DMA_InitTypeDef DMA_InitStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

TIM_InitStructure.TIM_Period = 72 - 1;
TIM_InitStructure.TIM_Prescaler = 40 - 1;
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStructure.TIM_RepetitionCounter = 0;

TIM_TimeBaseInit(TIM4, &TIM_InitStructure);


DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&DAC->DHR12RD);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(DAC_Buffers);
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = sizeof(DAC_Buffers) / sizeof(DAC_Buffers[0]);
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

DMA_DeInit(DMA1_Channel7);
DMA_Init(DMA1_Channel7, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel7, ENABLE);
TIM_DMACmd(TIM4, TIM_DMA_Update, ENABLE);

//TIM_Cmd(TIM4, ENABLE);

とりあえずTIM4とDMA1-7を使いました。予めuint32_tでバッファを宣言しておく必要があります。今回は500サンプルのバッファを作りました。500サンプルで50Hzになるように、TIM4を初期化します。TIM4はタイミングを作るだけなのでNVICなどは必要ありません。
次にDMAの初期化ですが、これも他の用途と全く同じです。ただし、普通はSTM32でペリフェラルとDMA転送する場合はByteが多く、たまにHalfWordを使う程度ですが、今回は珍しくWord転送です。これはDACに2chを一気に渡すために32bitのレジスタを使うためです。
とりあえずこの時点でDMA自体は有効にしておき、TIMのDMAトリガ出力も有効にしますが、TIM自体は後ほど有効化します(データを作ってから動かします)。

int i, l = sizeof(DAC_Buffers) / sizeof(DAC_Buffers[0]);
const float pi = 3.14159265f;
for (i = 0; i < l; i++) {
    float f = (float)i / l;
    f = sinf(f * pi * 2);
    
    uint16_t da1 = (uint16_t)(2047 + (2047 * +f));
    uint16_t da2 = (uint16_t)(2047 + (2047 * -f));
    DAC_Buffers[i] = da1 | da2 << 16;
}

TIM_Cmd(TIM4, ENABLE);

正弦波を作るに当たり、今回はsinf関数を使用しました。そのためmath.hをインクルードしておく必要があります。マイコン内で浮動小数点を扱えなかったり、扱いたくない場合はExcel等でテーブルを作ってやることもできますが、コピペしたりデータの管理が面倒なので、今回はマイコン内で作りました。どうせ起動時に1回実行するだけですし、動作確認用に動かすだけですから、ちょっと時間食うくらいは許容します。DAC1とDAC2では位相を逆にしています。
DACのDHR12RDは右詰め12bitで、下位16bitがDAC1、上位16bitがDAC2に出力されています。ということで32bitのデータバッファにビットシフトして与えています。
この辺りは趣味でやる以上はある程度個人の好みに実装して下さい。例えば16bitの2次元配列でDAC1とDAC2を分離するといったやり方もアリだと思います。

最後にTIM_Cmdでタイマを走らせて終了です。


STM32F1のペリフェラルは結構いろいろな組み合わせ方ができて、特にTIMはタイミングを作ったり、他にもいろいろな機能があります。場当たり的に必要な機能を実装していくとあっという間に使い切るので、うまく使いまわせるところは共通で使っていく必要があります。

TIMの機能
・タイマ割り込み
・PWM出力
・パルス幅計測
・DMAタイミング生成
・他のタイマの開始/停止
・その他

タイマの開始/停止は、例えばTIM2でPWMを作り、PWMがHighの時だけTIM3を走らせる、みたいな事ができます。5kHzのPWMを正確に50パルスだけ出したい、というような使い方ができます。
期限とか気にせずに考えてる時は楽しいんですけどね。いざ使おうとすると大変です。

STM32F1のDAC

ADCを試したいが、適当なアナログソースが無いので、内蔵しているDACから波形を出力することにした。

まず初期化。

/*
    DACを使う場合、GPIOはAINで初期化すること
    (RM0008日本語訳Rev11, 12.2(p251)による)
*/

GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitStructure;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);


DAC_InitStructure.DAC_Trigger = DAC_Trigger_None;
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None;
DAC_InitStructure.DAC_LFSRUnmask_TriangleAmplitude = DAC_TriangleAmplitude_4095;
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Enable;

DAC_Init(DAC_Channel_1, &DAC_InitStructure);
DAC_Init(DAC_Channel_2, &DAC_InitStructure);

DAC_Cmd(DAC_Channel_1, ENABLE);
DAC_Cmd(DAC_Channel_2, ENABLE);

とりあえずDAC1, DAC2の両方を初期化してみる。
DACの出力ピンは固定で、リマップとかはできない。
値を出力するにはDAC_SetChannel1Data(DAC_Align_12b_R, val);のような関数を使う。この例では右詰めだが、左詰めにすると16bitの下位4bit切り捨てと同じような動作になるので、16bitのWAVEデータを出したりするには左詰めが便利。

DACのOutputBuffが有効なときは最小で5kΩ、無効なときは最大で15kΩになるらしい。有効な時の最大インピーダンスは書かれていない。バッファを有効にしてもせいぜい2倍とか3倍しか変わらないが、ないよりはマシかな。


次はを実装する。正弦波あたりがほしいので、TIMとDMAで流し込む感じになるのかな。

超音波

STM32F1 TIM1で40kHzのPWMを作ってみた。TIM1は逆位相波を出力できるので、だいたい6Vppくらいのパルスを作れてる予定。オシロではGND基準の1ピンしか見てないからVDDの3.3Vあたりだけど。
FreeRTOSのタスクで5mSくらいPWMを出した後に500mSディレイという感じで出力。




超音波の送信と受信はだいたい18-20cmくらい離して向かい合わせでおいてある。20cmで音速だと600us近く。送信(赤)と受信(黄)の違いはだいたいそれくらいだと思う。
下の画像は水色が無風時の位相、黄色はR側からTに向かって軽く息を吹いてる。だいたい1usくらい遅れてる。STM32F1は確か2Mspsが最高だから、分解能0.5息速度くらい。

STM32F4だと2.4Mspsが最高で、並列だとその倍くらいかな?。やはり超音波風速計を作るならF4が必要かも、と思いつつ、僕はF4を使ったことがないので、今からF4を始めるくらいならF3を使ったほうがいいかもしれない。まぁどうせならPSoCのほうがいいような気もするが。
どっちにしろアナログ回路はある程度必要だから、いっそのこと数MSPSでサンプリングできるADCを買ったほうが早いかもしれない。


使用するオペアンプについて
とりあえずざっとTRの位置を変えた感じでは、最小で50mVppくらいが出てきてる。最大100倍くらいのゲインがあれば十分かも。これくらいならオペアンプ1段で十分だと思う。4回路オペアンプなら4ch受けれる。超音波距離計の回路では数千倍-数万倍くらいに増幅している例もあるみたいだけど、それは往復で10mくらいを飛ばすからであって、風速計みたいな最大でも40-50cm程度ならそんなにゲインは必要ない。
そもそもTR間が15cmくらいなら増幅無しでADCに繋いでもいいんじゃないかってくらいの電圧がある。インピーダンス高すぎてダメだろうけども、試す価値はあるかも。

STM32F1のリソースだと、出力が3系等、受信が8系統くらいまでかな。
TR間の距離を予めキャリブレーションして、音速は別のセンサで温度を計測する、みたいな構成なら送受信1対1で3セットあれば足りるかもしれない。おそらく市販の超音波風速計はこの構成だと思う。常識的な温度範囲なら熱の変形とかはあまり気にならないだろうし。


とりあえず、まずは送受信1対1で位相差を計測するところから始めるかな。位相差をF1の最大性能で測るとなると、F1のペリフェラルをキッチリ設定してやる必要がある。まずそこが難関。

2016年7月7日木曜日

ポップコーンの作り方

注意:僕はポップコーンの作り方をちゃんと調べたことがないので、間違ってたらごめんね

最近、よくポップコーンを作って食べてる。amazonで600円/1kgの袋を何袋か買っておくとかなり持つ。一袋だけでも数週間食べれたりする。

フライパンだと重いので、アルミ鍋とか使うと良い。高さが半径以上あると良いと思う。結構膨らむので。

手順
1) 鍋に油と塩を入れる。それぞれ好みの量を入れる。塩は結構てきとーに入れても大丈夫。油の量も決まった量というのは無いと思うが、油が少ないと食べるときにベタベタしなくていい。油が多いと若干甘みが出るのと、作るのが楽になる。
2) 鍋にポップコーン種を入れる。量はポップコーンが1.5段に満たない程度が良い。少ないとできた時の嬉しさが少なくなる。多すぎると綺麗に膨れなくなる。
3) 加熱する。ガスコンロでちょっと強めの弱火で火炙りにする。この際混ぜ方に工夫が必要。弾ける前は水平に加速してから垂直に加速する。イメージとしては「中華鍋でチャーハンを作る」感じ。この時点で揺する目的は種の上下をひっくり返すこと。ある程度連続して弾けるようになると、次は上下の加速度を与える。はじけ始めた時点で揺する目的が変化し、質量の違いを利用して破裂済みと未破裂の種を分離することに有る。質量の大きい未破裂の種を振動で下に落とすことにより、鍋に触れさせて加熱する。
4) 破裂がおおよそ止まったところで加熱を終了する。この時点では不発弾が加圧状態にあり、文字通り一触即発の状態なので注意すること。
5) これは好みになるが、粗熱を取って不発弾を安定させた後、蓋を開けて鍋の中の水蒸気を逃がす。そしてコンロに火を入れて加熱する。こうすることによりポップコーンの水分を飛ばし、パリパリとした食感を出す。

ポップコーンはポテチみたいに食べてて手がベタベタになるというのが少ない(油の量による)。またコスパもかなり高いので、結構量を食べてもそんなに高くない。鍋1杯分で数十円程度。
でも気が付くとあっという間に鍋が空になる。多分ゾンビってこんな気分だと思う。無意識のうちに手が動いて食べてるの。


ポップコーンのカロリーは油が結構多いみたい。なので油を少なめで作ればその分カロリーが減るし、手がベタベタになることも防げる。
油を使用しないで作ることも可能だが、これは結構たいへん。油はコゲを防ぐだけではなく、熱伝導物質としての役割が強い。
ポップコーンの種は球体に近似でき、平面の鍋には点で接触している。この状態で加熱した場合、1点だけが加熱し、そのほかの部分は1点からの熱伝導に依存することになる。
油を使った場合、鍋と面で接触できるため、効率よく加熱することができる。油を多く入れると作るのが楽になるのは、加熱が効率的になるため。
それと、一点で加熱した場合、その点がどんどん高温になり、やがてその部分が炭化する。炭化というのは沸点の高い物質だけが残り、それ以外が昇華する現象で、要はスカスカになる。そうすると気密を保つことができなくなるため、加圧されなくなり、破裂することもなくなる。

要するに均一に加熱できればいいわけだから、いろいろと工夫のしようはある。
例えば一点が加熱され続けないように常に撹拌するという物。
他には熱伝導の手段を変更するのも有る。オーブントースターに入れて放射で全方位から温めることも可能だと思う。他にも融点の高い液体で加熱するのもアリ。ポップコーンは250℃あたりで加熱すれば破裂するそうだから、280℃くらいの天ぷら油で温めるとか、液体の錫につけても良いかもしれない。ただしポップコーンは破裂した時に急激に体積が増えるから、液体にドブ漬けするのはちょっと怖い。あと食品なのでそのあたりの注意も必要になる。
手っ取り早いのは、ヒートガンで300℃くらいの空気を入れること。ただし熱が逃げないように、かつエアフローが妨げられないようにする必要がある。金網ボウルとかに入れてもいいが、保熱されないので時間がかかる。


鍋を振ってる間は他の作業ができないので、気分転換にちょうどいい。いいアイディアが浮かんだらポップコーンを食べながら作業したり、何も出なくてもポップコーンを食べながら考えても良い。
ポップコーンは素晴らしい。ポップコーンを讃えよ!

STM32F1のUSART(DMA)

STM32F1にFreeRTOSをポーティングしたついでにUSARTもいじってる。他のサンプルを見てないのでどういうやり方が正しいOSの使い方なのかわからないけど。

ハードウェアの初期化部分は通常の非OS環境と同じように使っていて、非OS環境でも使えるので貼っておく。


2016年7月6日水曜日

FreeRTOSのポーティング

STBeeにFreeRTOSをポーティングしてみる。
メモ帳代わりに変更点をブログエディタに書いてるので時系列にはなってるけど内容はまとまってない。暇な時の読み物にでも。


開発環境はGCCを使用している。
とりあえず普段使っている雛形を元に、OSを追加していく形で実装した。
この雛形について。USART1による通信やLチカを実装済み。協調的マルチタスクを自前で実装済みで、USARTからのコマンドの受け取りとかはマルチスレッドで行うことができる。センサロガーとかとして使う場合はタイマ割り込みの中でSPI通信とかを叩いていた。とりあえずマイコンの動作はできているのが確認できている。

FreeRTOSは各自で探してもらうとして、執筆時点ではv9.0.0が落ちてきた。RTOSなるものは初めて使うのにいきなりメジャーアップデート直後とかこの時点で不安が。

今回は初めてRTOSを使うことも有り、動作するかもわからないのでOSはプロジェクトのフォルダの中にコピーした。ちゃんと使うときは、OS自体はプロジェクトとは別においておくのがいいらしい。

プロジェクトのディレクトリの中にinc, lib, srcのディレクトリが有り、他にmakefileが入っている。StdPriph_Driverとかはlibに入っている。とりあえずlibの中にFreeRTOSディレクトリを作り、その中にFreeRTOS/Sourceの*.cとincludeディレクトリをコピーする。更にportableディレクトリを作り、Source/portableのMemMangをコピーする。
OSの機能はこれで足りるらしい。

とりあえずこの時点でmakefileに./lib/FreeRTOS/includeをインクルードパスに追加する。それと./lib/FreeRTOS/*.cをソースファイルとして追加する。

次にmakeする。すると予想通り「FreeRTOSConfig.hが見つからないぞ」とエラーが出る。あとはエラーを消していく作業をすれば、最低限ビルドが通るようにはなるはず。

まずはFreeRTOS/Demo/CORTEX_STM32F103_GCC_Rowley/FreeRTOSConfig.hを./inc/にコピーしてビルド。portmacro.hが無いというエラーが出た。
FreeRTOS/Source/portable/GCC/ARM_CM3のportmacro.hをincにコピー。
これでビルドが通った。警告も特に出ている様子はない。

ここでバイナリをSTBeeに書き込む。RTOSを追加しただけなので、望む動作は雛形と変わりない。とりあえずデバッグメッセージやLチカは正常に動作している。

次はRTOSを動作させる番。

まずFreeRTOS.hとtask.hをインクルードする。すでにインクルードパスにincludeディレクトリを追加しているので問題なくビルドが通るはず。


とりあえずxTaskCreateとvTaskStartSchedulerを試す。てきとーな関数を作ってTaskCreateに渡す。その後でTaskStartを叩く。ビルドするといろいろ未定義エラーが出る。とりあえず真っ先に目につくのがメモリ関係かな。
ソースファイルにFreeRTOS/portable/MemMang/heap_1.cを追加する。メモリ(Malloc,Free)関係の未定義エラーはおおよそ消えた。あとはpxPortInitialseStack, xPortStartScheduler, vPortEnterCritical, vPortExitCriticalが未定義となっている。
potable/GCC/ARM_CM3/port.cを./lib/FreeRTOS/にコピーしてmakefileに追加する。
これでビルドエラーが消えた。STBeeにバイナリを書いて実行。xTaskCreateは通ったが、vTaskStartSchedulerを叩いてもタスクのデバッグメッセージは出てこない。
例外ハンドラに文字列出力を入れたところ、HardFault_Handlerが呼ばれているらしい。

FreeRTOSをSTM32F4にポーティングする - あくまで個人的メモ用ブログに#defineを消すとHardFaultする、というのが書いてあったので、#defineを3行追加する。
このマクロの意図するところは、FreeRTOSの関数名を変更するらしい。例えばxPortSysTickHandlerはマクロによりSysTick_Handlerという名前になり、これはCMSISのSysTick割り込み名となる。
ただしこの3個の関数はstm32f10x_it.cですでに作成されているため、ビルドエラーとなる。とりあえずit.cの方をコメントアウトすることで対応した。
それをビルドしたところ、今度はvApplicationStackOverflowHook関数が無いとエラーが出た。ということでとりあえず引数なし、戻り値なしで中で無限ループする関数を作り、UARTでHookした旨を表示するようにした。
これでビルドが通ったのでSTBeeに書いてみる。すると2個登録したタスクの内1個を実行し、StakOverflowHookが呼ばれた旨の表示が出た。

これの原因は簡単にわかった。以前に読んだページで、「printfは大量にメモリ使うからstack足りなくなるよ」と書いてあったのを覚えていたため。タスクの動作テストにはprintfで文字列を出していたから、それが原因だろうと思い、printfをputsに変更したところStackOverflowHookは引っかからなくなった。


タスク2個の内容はほぼ同じで、違いはデバッグメッセージだけ。中身は文字列を出力した後、while(1);で無限ループとする。協調的マルチタスクでこのコードを実行すると、最初に呼ばれた関数の中で無限ループに入り、次のタスクへ処理がわたらなくなる。
今回のFreeRTOSもまだSysTickの初期化は行っていないから、実質的には協調的マルチタスクとなっている、はずだった。しかしなぜかTask1もTask2もデバッグメッセージを出力している。
port.cのなかでSysTick関係のレジスタを叩いているようなので、この中で初期化を行っているのかもしれない。

Task1のループで500ms待って文字出力、Task2のループで1000ms待って文字出力、という機能を追加したところ、おおよそそれくらいのタイミングで文字が出てきている。ということでマルチタスクの動作はできているらしい。


とりあえず最低限のFreeRTOSの動作は確認できた。USART周りは低レベルで書いてあったりするけど、動作していることに違いはない。あとは低レベルな部分をRTOSで書き直したりすれば良いのかな。
RTOSを使えば協調的マルチタスクと違って、勝手にOSがマルチタスクっぽくやってくれるので、気を使うところが少し減るかも。OSはOSで面倒な部分もあるんだろうけど。
でも最低限動作はしてるんだから、少しずつ改善していけば使えるようになるはず。



今回ハマったところまとめ
・FreeRTOSのデモを使わなくても、すでにあるコードに追加して動作させられる
→GCC使ってる人はデモ使うの面倒らしいし
・OSが使う割り込み3個はマクロで実装

以外に少ない。思ってたより簡単だったかも。

2016年7月5日火曜日

F-16 完全マニュアル (Owners' Workshop Manual)

「F-16 完全マニュアル (Owners' Workshop Manual)」という本があり、この本にはF-16の様々なことが書いてあります。もちろんテキストだけではなく、図や写真なども大量に掲載されています。
個人的に気になった写真・図等をまとめておきました。表にはまとめきれないほどに大量の写真等があります。