2016年11月30日水曜日

第4回:関数電卓で行う衛星位置の計算

さて、「関数電卓で行う衛星位置の計算」シリーズは前回で一応の完結をみました。が、前回最後に書いたとおり、せっかく衛星の位置がわかったんだからどの方向に見えるかも計算してみたいと思います。

しかし前回計算した南緯51度, 西経10度というのは日本から見ればほとんど地球の裏側であり、さすがにこれの方位や仰角を計算しても面白くないでしょう。
ということで、前回、前々回の復習も兼ねてもう一度衛星の位置を計算してみましょう。確実にISSが見える位置にいてほしいので、とりあえずJAXAのWebサイトで可視な時間を調べておきました。

軌道要素は以下を使用して下さい。
ISS (ZARYA)             
1 25544U 98067A   16331.50278528  .00003330  00000-0  58332-4 0  9995
2 25544  51.6438 328.6268 0006073 257.1648 241.1942 15.53732614 30231

観測日時:2016/12/04 17:01:30 JST

まずは第1回を参考に各要素を取り出してみて下さい。その後、第2回、第3回の手順のとおりに計算し、ECEF座標を計算して下さい。BLHまで計算する必要はありません。

まずはECIです。答えを書いておくと、僕が計算した結果はX = 4549, Y = -2653, Z = 4280といったあたりです。PreviSatの結果はX = 4543, Y = -2639, Z = 4287といったあたりです。だいたい17kmくらいの差がありますが、誤差の範囲だと考えましょう。

充分に問題のない数字であれば、ECEFまで計算を行って下さい。何百kmもズレているようであればどこかおかしいはずなので、もう一度計算してみて下さい。

参考までに、ECEFはおよそX = -3776, Y = +3671, Z = +4280あたりになります。

観測地点の決定

さて、いよいよ方位と仰角の計算に入ります。が、その前にまずは観測地点が何処かを決めておく必要があります。とりあえず北緯35.71, 東経139.81を使用して計算しようと思います(おおよそ東京スカイツリーのあたりです)。

緯度経度は今後頻繁に使うので、メモリに保存しておきます。といってもA, B, Cは衛星のECEFを保存していますし、x, yは今後使用しますから、D, E, Fのいずれかになります。とりあえずDに緯度、Eに経度を保存しておきましょう。

[35.71][STO][sin]

[139.81][STO][cos]

観測地点の位置ECEFを求め衛星との差をとる(ECEF)

まずは衛星と観測地点の差を計算します。衛星のECEFから観測地点のECEFを引いた差を求めることになります。
本来は観測地点のECEFを計算した後に差を求めるのがわかりやすいのですが、現在自由に使えるメモリが限られているので、観測地点のECEFを計算しつつ、同時に衛星との差も計算していきます。

緯度経度からECEFを求めるにはRec関数を使用します。Rec関数は半径rと角度θからx, y要素を計算するもので、sinとcosを同時に計算する事ができる関数です。sinとcosを個別に計算しても良いのですが、せっかくRec関数があるのでそれを使用します。

まずは緯度からECEFのZを求めます。
[SHIFT][-][6371][SHIFT][)][ALPHA][sin][=]
x = 5173.135203
y = 3718.643997

yがECEFのZとなるので、衛星のECEF、メモリCから引いた差をメモリCに入れておきます。
[ALPHA][x-1][-][ALPHA][S←→D][STO][x-1]
C = 561.235594

次に経度からECEFのXとYを求めます。
[SHIFT][-][ALPHA][)][SHIFT][)][ALPHA][cos][=]
x = -3951.802836
y = 3338.350217

メモリx, yはそれぞれECEFのX, Yです。Zと同じように衛星との差をそれぞれのメモリに入れておきましょう。
[ALPHA][(-)][-][ALPHA][)][STO][(-)]

[ALPHA][°'"][-][ALPHA][S←→D][STO][°'"]
A = 175.3075856
B = 332.5032403

ENUを求める

ENUは適当な日本語訳が見当たりませんでした。近い概念としてはEND, 局地水平座標系がありますが、最後の1文字が違う通り、1軸の解釈が違います。ENUはEast, North, Upの略で、東にいくら、北にいくら、上にいくら、という情報です。対してENDはEast, North, Downの略で、東にいくら、北にいくら、下にいくら、という情報です。
ベクトルの減算と回転で得られる値はENUですから、ここではENUを使います。

さて、ENUの計算ですが、これは「ベクトルの減算と回転」により計算します(他の方法もありますが、関数電卓向きではないのでここでは扱いません)。
ベクトルの減算はすでに先程済ませたので、あとは回転を行うのみです。ベクトルの回転は第3回で行ったECIやECEFの計算と同じです。ただしその時はX軸とZ軸の回転を使いましたが、今回はY軸とZ軸の回転です。

まず最初にZ軸で-Lon分を回転させ、その次にY軸でLat分を回転させます。

LonはメモリEに入っていますが、ZはLonの逆に回転させる必要がありますから、まずはLonを反転させます。
[-][ALPHA][cos][STO][tan]

[ALPHA][(-)][cos][ALPHA][tan][)][-][ALPHA][°'"][sin][ALPHA][tan][STO][)]

[ALPHA][(-)][sin][ALPHA][tan][)][+][ALPHA][°'"][cos][ALPHA][tan][STO][°'"]

[ALPHA][)][STO][(-)]

次にLatの回転を行います。
[ALPHA][x-1][cos][ALPHA][sin][)][-][ALPHA][(-)][sin][ALPHA][sin][STO][)]

[ALPHA][x-1][sin][ALPHA][sin][)][+][ALPHA][(-)][cos][ALPHA][sin][STO][(-)]

[ALPHA][)][STO][x-1]
A = 393.07277, B = -367.13236, C = 408. 636965

以上でENUの計算が終了です。ただし軸の取り方がちょっと直感とは違うと思います。A(X)がUp, B(Y)がEast, C(Z)がNorthの方向になります。

方位と仰角を求める

お疲れ様でした、あとは方位と仰角、ついでに距離の計算を残すのみです。これはECEFからBLHを計算したときとほぼおなじ計算です。

まずは方位を計算します。
[SHIFT][+][ALPHA][x-1][SHIFT][)][ALPHA][°'"][=]
x = 549.3362752
y = -41.93752609

yが方位ですが、負数になっているので360に方位を加算して最終的な方位とします。とりあえずメモリDにでも入れておきましょう。
[360][+][ALPHA][S←→D][STO][sin]
D = 318.0624739

次に仰角を計算します。
[SHIFT][+][ALPHA][)][SHIFT][)][ALPHA][(-)][=]
x = 675.4824545
y = 35.5852838

yが仰角なので、メモリEあたりに入れておきましょう。
[ALPHA][S←→D][STO][cos]

そしてxに入っている数字が彼我の距離となります。ついでなのでメモリFあたりに入れておきましょう。
[ALPHA][)][STO][tan]

ということで、メモリD, E, Fにそれぞれ方位、仰角、距離が入りました。それぞれ318°, 35°, 675kmとなりました。前例に従いPreviSatの値を例に出すと、それぞれ321°, 34°, 688km と言ったところです。方位で3°ほど、仰角で1°ほど、距離で10数kmほどズレていますが、肉眼で観察しようと言う程度であれば数度の誤差は問題ありません。距離に関しては何割もズレていなければ大して問題になりません。

最後に

以上で全4回に渡って続いた「関数電卓で行う衛星位置の計算」シリーズは終了となります。
実際のところ、既知の衛星の位置を電卓で計算してもあんまり使い所はありません。例えばISSが見れる日時を知りたければJAXAのWebサイトで探したほうが楽ですし確実です。衛星がどこにいるかを調べたい場合でも、PreviSat等を使えば簡単に知ることができます。
しかし、「宇宙?人工衛星?なんか難しそう。分かんないや」と思っていても、実は電卓で計算できるんだよ、というところから宇宙を身近に感じる人がいるかな、と思ってこのシリーズを書いてみました。

2016年11月27日日曜日

第3回:関数電卓で行う衛星位置の計算

前回に続き衛星の位置計算です。

前回は軌道面上の2次元位置を計算しました。今回はBLH(緯度経度高度)の計算までを行います。


2016年11月25日金曜日

第2回:関数電卓で行う衛星位置の計算

さて、前回に引き続き関数電卓で行う衛星位置の計算です。

前回、「次回はECIまで計算するよ」と書きましたが、ECIの前の段階までに変更となりました。ということでECI(からBLHまで)は次回となります。今回は軌道面上での2次元位置を計算するところで終了となります。


まず、その前に、いくつかの前提を説明しておきます。

1. 使用する電卓
使用する電卓はもちろん関数電卓ですが、今回はCASIO fx-JP900というモデルを使用します。変数が最大10個ほど(A-Fとx,yにMとAns, preAnsが)使用できます。
他の電卓には無い機能をつかったりしているかもしれません。その点はご了承下さい。
以降、このシリーズではキーの操作はすべてこの電卓のものです。

2. 精度
今回、衛星の位置は直交座標時の距離で40km、緯度経度時の角度で5度程度の誤差が発生するはずです。天体望遠鏡で撮影したい場合やレーザー通信を行いたい場合は問題が有る精度ですが、そもそもそんなことには使わないと思うので、問題ないこととします。


ということで、実際の計算に入ろうと思います。必用な値は前回書いておきましたので、別ウインドウでその数字をいつでも見れるようにしておくと便利だと思います。


2016年11月22日火曜日

第1回:関数電卓で行う衛星位置の計算

おわび

今シリーズで扱う計算式ですが、式自体に大きな誤りは無いと思いますが、手順に問題があります。既知の問題として、離心率が大きい軌道を計算した場合に不正な値となります。以後で使用する軌道の離心率は充分に小さいので大きな問題とはなりませんが、追って修正したいと思っています。
***


何回かの連載で「関数電卓で行う衛星位置の計算」というネタを扱おうかと思います。途中で失速するかもしれませんが。。。

なお、僕は衛星とか軌道とかちゃんと勉強したこと無いので間違ってたらごめんなさい。


初回の今回は起動計算は行いません。
軌道計算に必用な情報を集めるところから開始します。

これからのシリーズで使うデータは以下のTLEを元にします。

ISS (ZARYA)             
1 25544U 98067A   16326.55347831  .00003030  00000-0  53814-4 0  9995
2 25544  51.6454 353.2810 0006168 238.1568 278.5578 15.53693589 29460

見て分かる通り、ISS(国際宇宙ステーション)のデータです。

軌道計算に必用な要素は1)Epoch Time, 2)BSTAR drag, 3)軌道傾斜角, 4)昇交点赤経, 5)離心率, 6)近地点引数, 7)平均近点角, 8)平均運動 の8要素になります。
さらに、観測日時であるとか、計算に必用ないくつかの定数がありますが、定数については必要になった時に説明していきます。

また、観測日時については2016年11月22日 21時53分15秒(JST)を使用します。このブログを書いている時間です。もっとも、今回は観測日時の情報は使いませんが。


さて、突然ですが参考資料です。
きどうようそのひみつ
・人工衛星位置推算の実際(国土情報処理工学の"人工衛星の位置推算"というリンク)

前者は今回説明することをわかりやすく説明してくれます。まずはここを読めばTLEに何が書いてあるかわかります。
後者で扱っていることは今回は扱いませんが、要素の名前をこの資料と一致させています。


とりあえずTLEから情報を抜き出してみます。

Epoch TimeET16年326.55347831日
BSTAR dragM20.53814*10^-4
軌道傾斜角i51.6454
昇交点赤経Ω0353.2810
離心率e0.0006168
近地点引数ω0238.1568
平均近点角M0278.5578
平均運動M115.53693589

基本的にTLEからのコピペです。同じ数字があるはずですから、探してみて下さい。
例えばBSTAR dragはTLEでは先頭の"0."が省略されています。またこの値は指数ですが、"e"もしくは"*10^"が省略されているので書き加えています。
離心率についても同様で、この値は指数ではありませんが、0以上1未満であることが保証されているため、先頭の"0."は省略されています。
その他、EpochTimeは年の下2桁のみ書かれています。その為1999年から2000年へ切り替わるタイミング等で問題が発生する可能性があります。もっとも、次回は2100年でまだ80年以上先ですから、当分は気にしなくてもいいでしょう。もちろん、「俺の作ったプログラムは100年以上使われるはずだ!」という人はちゃんと考慮しておく必要があります。今回は関数電卓で計算する以上、途中で人間が適切に判断するはずだという期待において気にしないことにします。


地球直径や重力定数といった定数類を除けば、以上の値があれば衛星の位置を計算することができます。というところで、今回はおしまい。
次回は衛星の位置をECIという座標系で表現できるところまでを行いたいと思っています。
その後でECEFやBLHになりますから、おそらく第3回あたりで衛星の位置を緯度経度高度に算出できていると思います。

2016年11月13日日曜日

jsOrbitの隠しコマンドとか近況報告とか

jsOrbitですが、更新履歴を信用するなら5月の更新が最後だそうです。やばい。何がヤバいって軌道表示アプリなのに軌道要素が5月のデータ。

で、jsOrbit側の更新はもう少しあとになりますが、実は隠しコマンドが実装してあって、こいつが役に立つようにローカル側のプログラムを作りました。

隠しコマンドというのが、TLEデータのファイル名を与えるとそのファイルのTLEを読み込むというものです。ただ、いままでそのデータセットを作っていなかったので隠されていました。

今回、TLEデータをフィルタリングして必用なデータだけまとめるプログラムを作って、アップロードまでを一括して行えるようにしました。ということでTLEデータの更新はもうちょっと高頻度にできるかなと思います。

今のところはCubeSatとGeosynchronousOrbitしかありません。
http://www13.plala.or.jp/rian/jsOrbit/?set=CubeSat
http://www13.plala.or.jp/rian/jsOrbit/?set=GeosynchronousOrbit

CubeSatはすべてを網羅しているとは限りません。一応en.wikipediaのリストをコピペしてきましたが、IDの表記がない衛星も有るためです。
GeosynchronousOrbitは静止軌道ですが、これについては平均運動1±0.01かつ軌道傾斜角0±0.1の軌道要素を自動的に抽出しています。


とりあえず、既知の問題としてjsOrbitの不具合により、ファイル名を渡しても正常に読み込まれないという問題があります。
地図上には表示されるのですが、Satellite Listに表示されません。
ユーザーが取れる解決策としては、TLE dataの「読み込み」ボタンを押せば正常に表示されるようになります。
このあたりは近日中に更新しておこうと思っています。


ということでjsOrbitの近況報告でした。

2016年11月12日土曜日

Unityで今見てる物体を探す(あと終末のイゼッタ妄想)

このページを参考にした。
【Unity】視界の中央に入ったら光るCube - ちょっと未来


float minAngle = float.MaxValue;
GameObject selectedItem = null;

foreach (GameObject item in GameObject.FindGameObjectsWithTag("Selectable Items")) {
    float rad = Mathf.Acos(Vector3.Dot((
            item.transform.position -
            camera_.transform.position).normalized,
        camera_.transform.forward));

    if (minAngle <= rad) {
        continue;
    }

    minAngle = rad;
    selectedItem = item;
}

if (selectedItem != null) {
    // ほにゃらら 
}

Tagに"Selectable Items"が設定されてるゲームオブジェクトをすべてスキャンして視線と物体までの角度を計算する。現在のオブジェクトの角度が過去の角度の最小値よりも小さいならそのオブジェクトを変数に突っ込む。変数はnullクリアしてあるので、nullでないなら有効なオブジェクトが存在している。

このコードでは物体までの角度が一番少ないモノを得られるので、視界内に複数のオブジェクトが有るとき、視界の(というか画面の)一番真ん中に近い物体を得ることができる。ただし視界内にオブジェクトがない場合でも、FindGameObjectsWithTagに引っかかるオブジェクトが1個でもあればそれを検出してしまう。
それが嫌な場合、例えば「視線から20度以内に有る物体だけを検出したい」という場合なら、selectedItem != null && minAngle <= 0.35という風にする(minAngleはラジアンなことに注意)。foreachの中で角度の範囲を分岐する必用はない。


Unityはいままでほとんど触ったことが無かったけど、スクリプトをC#で書けるので楽。ドキュメントも結構しっかりしてるし(ウチは回線が細いのでオンラインドキュメントはつらいけど)。

以下終末のイゼッタネタバレ含むかも

Unityで龍脈みたいなの

アニメ妄想から脱線してなぜかUnity入門。でも一歩一歩コツコツとというタイプじゃないのであちこち脱線しながら。


Unityで龍脈みたいなのをどうやって作ろうか、と思ってTerrainを試してみた。



見づらいけど、上が地面の地形、下が「エネルギーレベル」の地形。レベルは高さが1で色もついてないからわかりづらいけど。

地形は画像データで作ってRAWに変換後にUnityのTerrainで読み込んでいるが、同じようにレベルも画像の濃淡で表現している。うっすらざーっと凹凸を作るのはデータ打ち込みだと面倒だけど、画像ならペイントソフトのブラシとかで簡単に作れる。

レベルを取得するには、取得した居場所の座標(X,Z)を引き数に与えてTerrainData.GetHeightを呼ぶ。戻り値はfloatで帰るので、Terrainの高さを1にしていれば0-1に正規化された値になる。
一応Terrainの設定でDrawをfalseにしてあるが、どれくらいリソースを食ってるのかは不明。Drawがfalseでも「不可視の地面」として当たり判定が存在しているので、何らかの形でリソースを食ってるのは間違いない。


この地形データで魔女が魔法を使うのに必用な魔力を持たせるとして、例えば箒で飛んでいるときにいきなり魔力が0になったときとか、どうすれば良いんだろう。いきなり0になることはないだろうから、エネルギーが薄くなってきたら戻って安全に着陸する、という感じになるのかな。

2016年11月11日金曜日

妄想:終末のイゼッタとか

6話までみた。まさかあんなことに。これだから戦争なんて(ry
前回の続きです。

2016年11月10日木曜日

Unityで地図タイルを読み込む



Unityで国土地理院の標高タイルを読んでみた。といっても座標変換とかやってないので歪んでるけど、まぁシミュレータとかじゃないのであんまり気にしないことにする。

地形考えるの面倒な人はこういう方法もアリですね。
もっとも地理院のデータとか使おうとするとデータ使用許可とか必用だから、外に出さない程度のモノに限定して使うのが無難。

Unityで読めるデータは1点あたり2バイトだけど、標高タイルは1点あたり6バイト以上あるのでデータ量がパない。回線の細い僻地民にはつらい。


C#のPanelでマウスドラッグのスクロールを行う

Panel panel1にPictureBox pictureBox1が乗っている。panel1はAutoScrollがtrueになっており、pictureBox1にはMouseDown, MouseUp, MouseMoveイベントが登録されている。

bool MouseScrollButtonIsDown = false;
Point MouseScrollButton_DownPos;
const MouseButtons MouseScrollButton = MouseButtons.Middle;

private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
    if (e.Button == MouseScrollButton)
    {
        MouseScrollButtonIsDown = true;
        MouseScrollButton_DownPos = pictureBox1.PointToClient(Cursor.Position);
    }
}

private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
    if (e.Button == MouseScrollButton)
    {
        MouseScrollButtonIsDown = false;
    }
}

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
    if (MouseScrollButtonIsDown)
    {
        Point currentPos = pictureBox1.PointToClient(Cursor.Position);

        panel1.AutoScrollPosition = new Point(
            -panel1.AutoScrollPosition.X + MouseScrollButton_DownPos.X - currentPos.X,
            -panel1.AutoScrollPosition.Y + MouseScrollButton_DownPos.Y - currentPos.Y
            );
    }
}

簡単に説明しておくと、まずpictureBox1の上でマウスを押下するとMouseDownイベントが呼ばれる。この際に指定したボタン(今回はスクロールホイール)かどうかを確認し、一致する場合は押下フラグがセットされる。この時、マウスが押下された位置を変数に保存する。また、ボタンを離すとMouseUpイベントが呼ばれ、フラグがクリアされる。
次にマウスが動いた場合、MouseMoveイベントが呼ばれる。この際に先程の押下フラグを確認し、押下されていない場合は処理を行わない。
スクロールをする場合、現在のカーソル位置と、押下されたときに保存したカーソル位置の差分を取り、その値を現在のスクロール位置に加算する。AutoScrollPositionはgetは負数で、setは正数で与える必要がある。

処理の内容を考えると、マウスが押下された位置と現在の位置の差分を加算しては加速度的にスクロールしてしまうのでは、と思ってしまう。しかしPanelの挙動からするとこの処理が正しい。

PanelのAutoScrollは、マウスホイールで上下移動は可能だが、縦スクロール専用のマウスでは横方向の移動ができない。ということで自前でスクロール処理を追加してやる必要がある。
この実装では若干GUIから予想した挙動と違う動作になるが、あまり大きな問題になるほどではない。

妄想:終末のイゼッタ

ハードディスクレコーダの容量がピンチで見ないアニメは消さねば、でも1話くらいは見ておこう、と見始めた終末のイゼッタ、結構ハマって何話か見た(まだ追いついてない)。
この魔女をゲームにしたら面白いだろうしすごいやりたいけどメディアミックスとか特に見当たらないので勝手に妄想。


2016年11月9日水曜日

C#のフォームの下にあるモノをクリックする

C#のFormにはTransparencyKeyというプロパティがあり、コレに色を設定するとそのフォーム上ではその色を透過色として扱うことができる(1bitα)。
コレを使うとフォームの後ろにあるボタンとかをクリックできるようになる。



上の画像ではForm1の手前にForm2があり、Form1にはいくつかのボタンが設置してある。
Form2はPictureBoxをDock:Fill, Modifiers:Publicで設置してある。
Form1のソースは以下の通り

using System;
using System.Drawing;
using System.Windows.Forms;

namespace TransparencyKey
{
    public partial class Form1 : Form
    {
        Form2 f2;

        public Form1()
        {
            InitializeComponent();

            f2 = new Form2();

            Bitmap bmp = new Bitmap(400, 400);
            using (Graphics g = Graphics.FromImage(bmp))
            {
                g.Clear(f2.BackColor);

                g.FillRectangle(Brushes.Lime, 100, 100, 100, 100);
            }

            f2.Owner = this;
            f2.TransparencyKey = f2.BackColor;
            f2.pictureBox1.Image = bmp;
            f2.Size += bmp.Size - f2.pictureBox1.Size;
            f2.Show();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Button b = sender as Button;

            if (b == null)
            {
                return;
            }

            label1.Text = b.Name;
        }
    }
}

Form2の初期化もForm1のコンストラクタ内で行っているが、Form1自体の動作としてはボタンが押されたらラベルにボタン名を表示しているだけ。
Form2についてはピクチャボックスに幅100x高さ100の塗りつぶし領域を作り、それ以外はBackColorで塗りつぶしてある。またTransparencyKeyにBackColorを指定してあり、BackColorの部分は透過とする。それからOwnerにForm1を指定し、Form1をクリックしても常にForm2が手前に居続けるようにしている。

上の画像では一部のボタンが緑の矩形に隠れているが、見えている部分であればクリックすることが可能。緑色の部分をクリックするとForm2がフォーカスする。
また透過した部分は常に見えている部分にマウスの処理が移動する。例えば後ろにウェブブラウザがあればリンクをクリックしたり、文字を選択したりすることが可能。


今回はPictureBoxに設定した画像はBackColorで塗りつぶしたが、例えばPNG画像で透過に塗りつぶした場所はPictureBoxでForm.BackColorに置き換わり、TransparencyKeyにBackColorが設定されていれば透過となる。


正直、Formの裏をクリックさせたいという需要がどこにあるかさっぱり想像できないが、昔何かをやろうとして調べたときに面倒そうだなぁと思って断念したことが有る。こんなに簡単にできるとは思わなかった。
背景をクリックしたいというのは、例えば半透明のフォームを最前面に常駐させて時計として使うみたいな用途が有るかもしれない。もっとも今回の方法ではそれは実現できないわけだけど。

2016年11月6日日曜日

Paint.NetでPNGやBMPで保存したときに非可逆な処理をされる

画像を加工したりして遊んでるときに気がついたことですが、Paint.Net(V4.0.12)でPNGやBMP等の「可逆圧縮」もしくは「非圧縮」で保存した場合に、細かなデータが消えてしまうことが有りました。ここでの細かいデータというのは高周波なデータという意味ではなく、ある地点と隣の地点の輝度差が赤だけ1明るいというようなデータです。
本来BMPファイルは輝度情報を直接保存しますから、微妙な輝度差が捨てられることは有りえません。PNGのフォーマットは詳しく知りませんが、可逆圧縮を期待して保存している以上は、微妙な輝度差であっても残っていてほしいものです。

結論から言うと、Paint.Netの保存オプションでビット深度を「自動検出」にしていると細かな輝度情報が消えてしまうようです。ということでPNGなら32bitやBMPなら24bitを明示的に指示して保存する必要があります。

この問題は保存フォーマットを自動で検出する際に明らかに使用している色が少ない場合はインデックスカラーが選択されるわけですが、RAWデータからカラーパレットを作る際に、まず輝度を平滑化して細かな輝度情報を捨てるという処理をしているようです。これについてはアルゴリズムの最適化の問題であり、そもそも輝度が1変化することに意味を持つ画像というのはあまりありませんから、バグというよりは道具を使う方の問題です。


あとコレはバグと言うには微妙な問題ですが、保存ダイアログでフォーマットをPNG、ファイル名の拡張子をBMPにした場合、拡張子はBMPでファイルフォーマットがPNGという残念なデータになってしまいます(おそらく他の組み合わせでもそのような挙動になると思います)。
そもそも近年の画像を扱うプログラムでは拡張子は無視してファイルヘッダのマジックナンバー等でフォーマットを見分けており、実質的に拡張子はプログラムに対する関連付け以上の意味を持っていません。ということでこれも使う側の問題といえばそうなのですが。

2016年11月1日火曜日

STBeeでFreeRTOSを走らせる

STBeeMiniのサンプルをベースとしてSTBeeで走るFreeRTOSの雛形を作る。


プロジェクトのトップを./とする。ダウンロードしたFreeRTOSのフォルダは単純にFreeRTOSと表記し、FreeRTOS/Sourceとかになる。

./libにFreeRTOSを作ってsrcとincを作る。
./lib/FreeRTOS/srcにFreeRTOS/Sourceの*.cをすべてコピーする。
./lib/FreeRTOS/incにFreeRTOS/Source/includeの*.hをすべてコピーする。
./incにFreeRTOS/Demo/CORTEX_STM32F103_GCC_RowleyのFreeRTOSConfig.hをコピーする。
./incにFreeRTOS/Source/portable/GCC/ARM_CM3のportmacro.hをコピーする。

makefileのインクルードパスに./lib/FreeRTOS/incを追加する。
また./lib/FreeRTOS/src/*.cをmakefileのソースに追加する。

とりあえずこの時点でビルドが通るようになってるはず。もちろんFreeRTOSが動くことはないが。もっとも、ビルドに通るとは言えまだまだ足りないものが有るので他にも色々追加していく。


./lib/FreeRTOS/portにFreeRTOS/Source/potable/GCC/ARM_CM3のport.cをにコピーする。またmakefileに追加する。
./lib/FreeRTOS/portにFreeRTOS/Source/portable/MemMangの*.cをすべてコピーする。いくつかのファイルがあるが、実際に使用するのは1つだけとなる。今回はheap_1.cを使用するため、それをmakefileに追加する。

この時点でvTaskCreate等も使えるようになった。が、まだOSを走らせるには足りないので修正を行う必要がある。


まず./inc/FreeRTOSConfig.hの最後に以下の3行を追加する。
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

これにより3個の関数で複数定義エラーが発生するので、./src/stm32f10x_it.cのSVC_Handler, PendSV_Handler, SysTick_Handlerをコメントアウトする。


vApplicationStackOverflowHookの未定義エラーが発生するので、適当なソースファイルにvoid vApplicationStackOverflowHook(xTaskHandle *pxTask, signed portCHAR *pcTaskName)関数を作っておく。これはFreeRTOSがスタックオーバーフローを検出して呼ぶ関数だが、常にこの関数が呼ばれるわけではないことに注意する必要がある。ということでそもそもスタックオーバーフローの検出を無効化しておくことにする。そのためには./inc/FreeRTOSConfig.hのconfigCHECK_FOR_STACK_OVERFLOWを0に設定する。

そしてやっとこの時点でFreeRTOSが最低限動作する状態となった。

FreeRTOSにタスクを追加するにはvTaskCreateを使う。FreeRTOSを開始するにはvTaskStartScheduler()を使う。


あとSTBeeMiniのサンプルはクロックの初期化が間違っている?PLLで6倍しなければいけないところを9倍にしているのでクロックが不適切。
system_stm32f10x.cのSetSysClockTo72関数で、RCC_CFGR_PLLMUL9となっているのをRCC_CFGR_PLLMUL6と変更すればいい。