2018年4月6日金曜日

BLH/UTM/MGRSデータセット

 緯度経度高度(BLH/LLH)とUTM、MGRSのデータセット。
 適当に乱数で作った8地点と、UTM/MGRS的にヤバそうな地点のフチの部分。数が少ないのであんまりテストデータには向かないかも。

 データは以下の構造体に入ってる。

typedef struct Position_data
{
    struct
    {
        double latitude;
        double longitude;
        double height;
    } LLH;
    struct
    {
        double x;
        double y;
        double z;
    } ECEF;
    const char *const MGRS;
    // if MGRS is null, UTM is invalid
    struct
    {
        uint8_t zone_lon;
        bool is_north_side;
        double east;
        double north;
    } UTM;
} Position_data;

 LLHはLatLonが度単位、heightがメートル単位、ECEFはすべてメートル、MGRSはdigit10(5:5)でゾーンはゼロ埋め、UTMは1メートル分解能。
 MGRSは文字リテラルへのポインタだが、UTM/MGRSに変換できない地域(北緯84度以上の北極周辺と南緯80度以上の南極周辺)の場合はMGRSにヌルポインタがセットされる。


 LLHとECEFの変換には以下のサイトを使った。
 Latitude/Longitude/Height <--> ECEF via J-Script BLH

 LLHとUTM/MGRSの変換には以下のサイトを使った。
 Convert between Latitude/Longitude & UTM coordinates


 とりあえずLLHとECEFは「GPSのための実用プログラミング」という本のソースコードを参考にしたプログラムでチェックしている。LLHとUTMはwikipediaの式を参考にしたプログラムでチェックしている。UTMとMGRSは上記リンクのJavaScriptコードを参考にしたプログラムでチェックしている。

 この本のプログラムでは、ECEF→LLHで、sqrt(x*x+y*y)<1の条件の際にheightの値が不正になる不具合がある(正確には、sqrtが数十cmを下回ると急速にheightのエラーが大きくなる)。某F氏が作者の承諾を得てC++に移植したコードでもそのまま残ってる気がする(未確認)。
 上記LLH-ECEF変換のサイトのJavaScriptコードを見てみると、sqrt(x*x+y*y)が一定以下の場合は特殊な処理を行っているらしい。とりあえずsqrt(x*x+y*y)<1e0の場合はheight = fabs(z) - a * (1 - f)で対応した。
 データセットでは(0オリジンで)2番めがx=1程度になるようにしている。3番目と4番目は地軸直上のそれぞれ北極側、南極側にしている。データセット2をPASSして3でFAILするならこの部分の問題がある。


 UTM/MGRSではノルウェー南西付近とノルウェー領スヴァールバル諸島の付近に特殊な処理をしている。データセットの内10セットはこの地域の経度方向の区切り付近のデータになっている。


 日本で位置情報を扱う国土地理院としては、災害時の地図情報としてMGRSを使いたいらしい。ただし、なぜかMGRSとは言わず、UTMと言っている。「昔はUTMを使っていたから、MGRSもUTMと言い張る」みたいな理論らしい。ミリタリーと名の付くものを嫌ったのか? とにかく、諸外国のUTMと地理院のUTMは別物なので要注意。
 地理院が防災・災害時にMGRSを使いたがっている割には、MGRSの計算方法とかの情報が極めて少ない。今回一番参考になったのはwikipediaの計算式と先のリンクのJavaScriptコードだった。大丈夫か国土地理院(地理院のWebサイトにあるPDFにUTM変換のJavaScriptコードが有るんだけど、非常にわかりずらい)。


/***

 最近、何もやる気でないけど、何もしないには暇すぎる、という状況。ということで暇つぶしに位置座標関係のソースコードを書いたりしてる。
 JSF++のコーディングルールを意識したコードを書いて、CppUnitでテストしている。TDDという程じゃないが。

 CppUnitはかなり使いづらい。基本的にCPPUNIT_ASSERT_EQUALを使って、多重定義で各種の型に対応しているようだが、例えばuint8_tを渡すと文字として処理される。結果、FAILすると期待値や結果がASCIIで表示される。期待値や結果が2とか8だったりすると、文字としては何も表示されない。わけがわからない。
 他にも、const char*と別のconst char*を渡すと、常にFAILする。これは「ポインタが渡されたんだな!よし、ポインタを比較するぞ!。別のアドレスを指しているな?FAILだ!!」みたいなことらしい。文字リテラルと文字リテラル同士の比較なら文字列として比較されるが、そんなもんはテストする意味ない。結局、trueと0 == strcmp(expected, actual)の比較を行うしか無い。ただし、boolの比較の場合、trueとfalseの表示ではなく、0と1で表示される。わけがわからないよ。

 テストと並行して開発していくと、コーディングよりもテストケースを作るほうがはるかに時間がかかる。コーディングのほうが時間がかかる場合は、そもそもコード規模が大きすぎる(あるいはテストが少なすぎる)ので、プログラムとして不適切。
 結果、コードの品質を保証するにはテストをするべきだが、テストケースを作る行為に非常にコストがかかる。テストをしたからと言って、テストした点以外の場所にでかい虫が隠れてる可能性もあるし。
 そりゃF-35も納期伸びてコスト上がるわけだ、みたいな感想。
 F-35はEO-DASで敵機や見方機の追跡、EOTSで地上目標の探知・識別、AESAで空中/地上目標の探知・識別ができるらしい。ということは、様々なデータを処理するためのソースコードが有るわけで、それについてもちゃんとテストが行われている。はず。テストデータだけでもいったいどれだけ有るのか。
 F-35のソフトウェアは開発中だから、「値下げしろ」と言われれば、ソースコード開発のどこかを切るしか無い(ハードウェアの設計は大体終わってるはずで、そこでコストカットは厳しいはず)。となると、「値下げしろ」と言われて以降のソースコードは、結構ヤバイのかも知れない。

 JSF++を読んでると、コード例で<LM_string.h>というヘッダがインクルードされてる。おそらくロッキード・マーティンが作成した文字列処理に関するライブラリなのだろう。こういうレベルから作っていくんだから、防衛装備品は大変だなぁ、と思う次第。
 例えばJSF++ではstdio.hは使用が禁止されている。他にも時間処理系のライブラリも使用禁止。そういうのを全部作らなきゃいけないんだから、大変だよなぁ。

***/


 データセットは500行くらい有るので以下。


2018年3月31日土曜日

日本周辺LatLon/MGRSデータセット

 久しぶりの更新です。年度末駆け込みエントリ。駆け込むような内容じゃないけど。


 LatLonとMGRSのデータセットを作ってみました。LatLonとMGRSの変換コードとかを書く人はプログラムのチェックとかに使ってみてください。後述するように、日本周辺しかカバーしていないので、ノルウェーとかあの辺りはカバーしていません。

 変換は地理院地図で手作業でやってます。なのでどっかミスってるかも。簡単にチェックした限りでは大丈夫だと思いますが。

 地理院地図


 * データ範囲
 16Nから56Nまで、120Eから156Eまでの矩形。
 GZD(grid zone designator)で51から56、QからUの範囲です。

 1列目:データ番号
 2列目:北緯
 3列目:東経
 4列目:MGRSコード(4+4digit)

0 44.96186516 153.1542827 56TNQ12167872
1 18.7714042 143.0560952 54QYF16737678
2 46.43152517 123.8722651 51TWM67014236
3 34.57617141 151.1546819 56SLD30732759
4 46.80607313 138.1273513 54TTS80828762
5 50.97522775 150.444036 56ULB20565018
6 42.34817779 153.0308327 56TNM02538843
7 28.306578 123.9823699 51RWM96313155
8 34.93075751 146.0695517 55SDU15026575
9 40.8256208 149.5492312 55TGF14962252
10 51.74925858 131.2469221 52UFC55103553
11 45.85395448 134.5753307 53TML67037790
12 18.35453617 136.3214108 53QPA39612991
13 32.71391875 123.9806842 51SWS91901999
14 22.55759173 143.1845675 54QYK24639619
15 28.52762554 139.3176863 54RUS35395680
16 24.13443419 153.2556589 56RNM25976913
17 30.44526514 132.6153275 53RKP71007054
18 39.47509494 126.0155309 52SBJ43287375
19 26.53095451 140.4341724 54RVQ43623461
20 16.44206381 124.1708383 51QXU24991819
21 17.78883466 147.1096265 55QEV11616682
22 32.24670767 152.1072662 56SMA15906813
23 26.37228208 135.0063959 53RNK00631691
24 52.45606586 142.9168586 54UXD30241349
25 34.61370299 150.7056936 56SKD89643259
26 43.2473821 125.2140275 51TXH79739066
27 21.25670397 134.2805044 53QMD25355072
28 50.14047509 125.5887281 51UXR84975745
29 38.2773181 120.8249588 51SUC09743882
30 44.53458017 129.6540982 52TEQ51963145
31 20.37395729 135.9317315 53QNC97235313
32 19.98191373 143.7686473 54QYH89731187
33 55.45592321 135.3999101 53UNB25294560
34 46.61256741 135.1153209 53TNM08836211
35 16.49164717 131.7868028 52QGD97522537
36 42.99508508 128.4849595 52TDN58016039
37 46.84641436 144.9882321 55TCM46619006
38 41.34109508 128.2280872 52TDL35417691
39 29.76365353 143.5512955 54RYT46689532
40 53.87005009 154.8711544 56UPE23027068
41 49.49386466 144.6834442 55UCQ32248493
42 43.36724466 129.3866424 52TEP31320167
43 34.01364544 139.2004572 54SUC33836512
44 39.56023913 137.6020481 53SQD23548218
45 26.59305316 142.3502875 54RXQ34454207
46 50.01263422 147.4384549 55UER31414012
47 33.34293228 123.3076479 51SWS28628934
48 29.11023704 122.2793269 51RVN29882041
49 35.32740487 126.8219815 52SCE02031152
50 54.11905341 143.0157117 54UXE31749864
51 28.36128872 120.203023 51RTM25854040
52 35.67839253 122.7087131 51SVV73644831
53 18.86014498 125.0677693 51QYA17858662
54 51.57016518 141.9936974 54UWC68861370
55 53.49512464 139.52631 54UVE02232836
56 30.7216343 153.6808136 56RNU65189895
57 38.19484205 135.3814567 53SNC33402750
58 46.71081244 143.8655927 54TYS19027701
59 22.80537597 126.2490146 52QBL17592460
60 23.64109735 145.6131361 55QCG58541517
61 21.97749607 122.8904952 51QVE88693034
62 40.97157846 155.1397791 56TPL80043780
63 55.63164644 150.4769347 56ULG41166797
64 34.11977345 141.4619827 54SWC42607553
65 37.60524134 126.0650445 52SBG40916606
66 28.02268182 136.9628807 53RPM92970126
67 40.45923319 148.6484249 55TFE39768003
68 38.9547951 128.7149847 52SDJ75301179
69 16.36678463 148.7955161 55QFU91771035
70 43.17482058 122.3411788 51TVH46458043
71 32.27967715 127.3031004 52SCA40197270
72 31.18582796 147.4932757 55REQ46995030
73 52.48425027 120.1984457 51UUU09771859
74 30.2980949 150.3743381 56RKU47475473
75 39.22704779 143.4443602 54SYJ10994482
76 28.08577903 140.7503122 54RVS75460672
77 23.51874727 131.4197272 52QGM47070302
78 17.16803188 147.2879577 55QEU30629816
79 45.07334959 122.4427623 51TVK56139124
80 43.36859054 142.8083557 54TXP46510333
81 26.76202605 130.7391816 52RFQ72936125
82 26.2274406 154.6776849 56RPQ67580195
83 49.09472783 128.2637125 52UDV46243824
84 31.29198709 155.4945263 56RQV37446464
85 51.58023875 121.8532846 51UVT20541497
86 48.23330635 123.6334546 51UWP47034242
87 45.35690253 135.3736399 53TNL29262266
88 55.94809064 145.7523634 55UDC22080100
89 42.65429001 150.3310178 56TKN81232587
90 47.6126432 128.498543 52TDT62317337
91 47.05889011 135.313476 53TNN23801175
92 19.56330254 135.301159 53QNB31586318
93 20.41021724 151.3785403 56QLH30815771
94 21.36599532 133.9048001 53QLD86456304
95 44.69100228 129.04793 52TEQ03794862
96 17.15687852 133.3283063 53QLU22189767
97 34.47526915 150.9530766 56SLD12011675
98 43.6421101 153.4284252 56TNP34553221
99 54.50784753 151.1125419 56ULF77794166

2018年3月14日水曜日

UARTのHALで直接割り込み

 久しぶりにHALでUSART割り込み使おうとして覚えてなかったのでメモ。

 HALのUSARTではTXE(Transmit Empty)とRXNE(Read Not Empty)のコールバック関数はない。これは、HALではHandleにバッファのポインタを渡し、HALの中でそこに(/から)転送する、という処理になっているため。
 なので、FreeRTOSのQueueとかで転送したい場合は、自分でUSARTx_IRQHandlerの中身を書く必要がある。

if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))
{
    extern osMessageQId stdin_queueHandle;
    uint8_t data = huart1.Instance->DR;

    xQueueSendFromISR(stdin_queueHandle, &data, 0);

    return;
}

if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TXE))
{
    extern osMessageQId stdout_queueHandle;
    uint8_t data = 0;
    BaseType_t status = xQueueReceiveFromISR(stdout_queueHandle, &data, 0);

    if (status == pdPASS)
    {
        huart1.Instance->DR = data;
    }
    else
    {
        __HAL_UART_DISABLE_IT(&huart1, UART_IT_TXE);
    }

    return;
}

 この処理はおそらくHAL_UART_IRQHandler(&huartx);の前に書く必要がある。
 また、TXEをチェックしてからRXNEをチェックすると、すでにRXNEがクリアされてる、という謎の挙動があるので、RXNEを先にチェックする必要がある。


 送信の場合は、xQueueSendでデータを入れた後に__HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);でTXEを有効化し、割り込みの中でDRに転送している。TXEが有効でかつDRにデータがない場合、たぶんずーっと割り込みが回り続ける。なのでQueueが空の場合はDISABLEで停止し、QueueSendの後に都度ENABLEする、という手順にしている。

 受信の場合は、適当な箇所で__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);で割り込みを有効化しておく。RXNEは、受信しない限り割り込みが発生することもないので、ずっと有効状態でいい。


 注意点はあまり多くないが、割り込みのチェック(__HAL_UART_GET_FLAG)に渡す値と、割り込みの有効化/無効化(__HAL_UART_ENABLE_IT等)に渡す値は別の値であることに注意する必要がある。

STM32 HALのタイムアウト

 HALのタイムアウト処理にガッツリはまって原因が判明するまでものすごい時間かかったのでメモ。


 HALのブロッキングな関数は引数の最後にタイムアウト時間を指定できる。これは典型的にミリ秒を指定する。以降とりあえずミリ秒とする。
 この値は例えば5を指定すると、ブロック時間は「5ミリ秒未満」という処理になる。「5ミリ秒以上」では無い点に注意する必要がある。
 より正確的に言えば、「5回目のTickでタイムアウトする」という処理になる。

 例えば、SPIで1バイトずつポーリングする場合、HAL_SPI_Receive(&hspi1, &buff, 1, 1);というようなコードになる。この場合、1ミリ秒未満、あるいは1回目のTickでタイムアウトする。大抵の場合はこの処理で問題ないが、ある程度の確率で、Receiveを呼んだ直後にTick割り込みが発生する場合があり、その場合はSPIの1バイトを受信する前にタイムアウトエラーでSPI通信処理が終了する。この場合、当然ながら通信が正常に行われていないから、buffには何も書き込まれず、不正な値となってしまう。
 buffのデフォルト値としてビジーの値を書き込んでいると、相手はビジー終了を通知しているにもかかわらず、自分側はビジー値を読んでしまう、という結果になる。
 これはTick割り込みが発生する直前にReceiveを呼んだ場合にのみ発生するから、ある程度確率的な話になる。ということで、それなりの高頻度で発生しながら、よくわからない挙動を示す、という結果になる。


 まとめると、タイムアウトの値には 最低限待っていてほしい時間 + 1 を渡してあげようね、という話でした。


 ちなみに今回は10.5MHzでSPIを動かしてましたが、8bitの転送には760nsecくらいの時間で終わります。1msecの間の特定の760nsecの間で処理が行われるとエラーになる、なので、1300分の1くらいの確率でしょうか。1秒間に14回程度の処理を行い、1回の中でビジーチェックが30回程度行われるとすると、1秒に420回程度の機会があります。ということで、3秒程度でエラーが発生する計算です。実際には10秒前後でエラーを起こしていましたから、当たらずとも遠からず、といった感じです。

 しばらく調べていて、ようやくビジーチェックが怪しいぞ、とあたりを付けた時はSPIクロックを下げたりも試したのですが、一向に改善しませんでした。外の問題ではなく、内の問題だったので、全く見当違いのところを調べていたことになります。

2018年3月11日日曜日

TAR

 TARファイルのはなし。

 tar(4)
 tar の構造


 基本的に1つの変数を除いて、すべてヌル文字終端の文字列として格納されて、その基数は8進。ただし幅が足りない場合はヌル文字を省略できる、という感じらしい。
 ヘッダは512バイトの固定長、データは512バイトを1ブロックとするが、ヘッダに元の大きさが格納されているので、詰め物を除去できないとかの問題はない。
 ヘッダの先頭から1024バイトの0(ヌル文字)が連続したら、そこをTARの終点とする、みたいな処理になってる。厳密に実装するとヘッダを読み込んだらまず512バイトすべて0か探索して、もしそうなら次の512バイトを読んで、それも0かチェックする、みたいな処理になる。ちょーめんどい。今回はsizeの最初の文字がヌル文字なら終了する、というふうにした。

 8進数が実際に使われてるの初めて見た。

 C#だとこんな感じでファイル一覧を吐ける。
 null終端文字列を切る方法を知らないのでてきとーに関数作った。IndexOfしてSubstringしてるだけ。

using (FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
{
    bool loop = true;

    while(loop)
    {
        byte[] buffer = new byte[512];
        fs.Read(buffer, 0, buffer.Length);
                    
        if(buffer[124] != '\0')
        {
            string name = Encoding.ASCII.GetString(buffer, 0, 100);
            string mode = Encoding.ASCII.GetString(buffer, 100, 8);
            string uid = Encoding.ASCII.GetString(buffer, 108, 8);
            string gid = Encoding.ASCII.GetString(buffer, 116, 8);
            string size = Encoding.ASCII.GetString(buffer, 124, 12);
            string mtime = Encoding.ASCII.GetString(buffer, 136, 12);
            string chksum = Encoding.ASCII.GetString(buffer, 148, 8);
            string typeflag = Encoding.ASCII.GetString(buffer, 156, 1);
            string linkname = Encoding.ASCII.GetString(buffer, 157, 100);
            string magic = Encoding.ASCII.GetString(buffer, 257, 6);
            string version = Encoding.ASCII.GetString(buffer, 263, 2);
            string uname = Encoding.ASCII.GetString(buffer, 265, 32);
            string gname = Encoding.ASCII.GetString(buffer, 297, 32);
            string devmagor = Encoding.ASCII.GetString(buffer, 329, 8);
            string devminor = Encoding.ASCII.GetString(buffer, 337, 8);
            string prefix = Encoding.ASCII.GetString(buffer, 345, 155);

            UInt32 data_size_byte = Convert.ToUInt32(size.clip_null_char(), 8);
            UInt32 data_size_block = (data_size_byte + 511) / 512;


            Console.WriteLine(name.clip_null_char() + " " + data_size_byte);

            fs.Position += data_size_block * 512;
        }
        else
        {
            loop = false;
        }
    }
}

 普段使ってる圧縮展開ソフトではTARにできなかった。ファイル数が多すぎたみたい。動画を切り出した連番画像なので3700個もある。
 今回はbashのtarコマンドで作ったけど、*.jpgで全部突っ込んだら単純なASCII文字列でソートした順番になった。自然数順じゃないので、必要に応じて0埋めとかにリネームしておく必要があるかも。ちゃんとしたやりかたはありそうだけど。

 すべて512バイト単位ってのは便利でいいなぁ。格納時に気をつければシーケンシャルリードできるし、まるちめでぃあかーどでも早そう。ファイル名を指定したランダムリードはクッソ遅そう。今回はシーケンシャルで十分だけど。


 とりあえず眠いのでマタアシタ~

電光掲示板


 64x32パネルが届いたので、2枚並べて128x32pxのディスプレイにしてます。
 とりあえず複数パネル直列は簡単なバグフィックスで動きました。やったね!(デバッグかなり手間取ったけど)。
 あとSPIでまるちめでぃあかーど互換めでぃあの読み込みができるようになりました。CubeだとFatFsのリードオンリー構成ができませんが、書き込みしないならWriteは呼ばれないので未実装です。
 あとTJpgDecを組み込んでJPEGファイルから電光掲示板のVRAMに画像を転送できるようにしました。

 画像の読み込みは1枚あたり20msecから200msecくらいです。ものすごい幅がありますが、画像の読み込みが20msec程度で、それより多くかかるのは、ファイルシステムの探索に必要な時間っぽいです。
 今は連番のJPEGをメディアに入れてるだけなので、1枚開く毎にファイルシステムの探索が必要です。Motion JPEGとかで持てば良いんでしょうが、ファイルフォーマット面倒臭そうだなーと思ってます。とにかく複数のファイルに分かれているのが問題なのであって、それが解決できれば済むので、TARのFatFsラッパーとか作れば解決しそうな気がします。そっちのほうが簡単そうだなーって気がします。


 さーて、電光掲示板、何に使うかなぁ。普通に画像とか表示すると発熱だけでも半端ないので、せいぜいちょっとした文字を表示するくらいしか使えないです。うーぬ。

2018年3月9日金曜日

メモ:INA226の読み方

 ストリナのINA226モジュールの。

 定義
namespace
{
uint8_t INA226_device_address = 0x80;

enum INA226_register
{
    configuration_register = 0x00,
    shunt_voltage = 0x01,
    bus_voltage = 0x02,
};
}

 おまじない
I2C_HandleTypeDef *hi2c = &hi2c1;

 レジスタの読み出し
int8_t data[2] = { 0 };
uint16_t *ptr = (uint16_t *)data;
HAL_StatusTypeDef status = HAL_OK;

status = HAL_I2C_Mem_Read(
                hi2c, INA226_device_address, INA226_register::bus_voltage,
                I2C_MEMADD_SIZE_8BIT, data, sizeof(data), 10);

if (status == HAL_OK)
{
    int16_t value = __REVSH(*ptr);
    printf("voltage: %.2f V\n", value * 1.25 / 1000);
}

status = HAL_I2C_Mem_Read(
                hi2c, INA226_device_address, INA226_register::shunt_voltage,
                I2C_MEMADD_SIZE_8BIT, data, sizeof(data), 10);

if (status == HAL_OK)
{
    int16_t value = __REVSH(*ptr);
    printf("current: %.2f A\n", value * 1.25 / 1000);
}


 CubeでI2Cの初期化を行ってしまえば、あとはMem_Readで読み出せる。いちおうI2CのEEPROMがターゲットだろうけど、普通にI2Cデバイスの読み書きに使える。
 エンディアンが違うのでREVSHで変換する必要があるのが面倒。

 INA226はリセット時で約900Hzの継続サンプリングモードに入る。電圧/電流共にサンプリングされるので、初期設定とかは特に不要。電力を読み出すならそのあたりはいろいろ設定する必要があるはず。

 ほんとはINA226のドライバクラスを作るべきなんだろうなー。メンドクセ