2015年12月31日木曜日

Visual Studioのコメント



Visual StudioではCのソースファイルでもマウスオーバーでいろいろ表示できる。あとTODOリストとかも作れる。けどC#と違いsummaryの自動入力とかはできないみたい。それから戻り値はそもそもカーソルを合わせられないので、戻り値の説明をreturnsで書いてもマウスオーバーでは表示できない。
そもそも何も書かなくたってマウスオーバーで宣言が表示されるんだから変数名で説明しろとかいろいろ言いたいことはあるだろうけど、とりあえずこういう書き方もあるんだよって。

Visual Studio限定なので、Doxygenみたいなマルチプラットフォームとかはあんまり無い。
使い所が難しそうだけど、とりあえず/// <summary>とかで書いておけば最悪Doxygenに移行したい時には正規表現の置換とかでなんとかなりそうな気もするので、場当たり的にコメント書くよりはマシだと思う。

2015年12月23日水曜日

SDカードの速度



SDカードの速度を測ってみた。512バイトだけシングルブロックライト、それ以外はマルチブロックライトで一気に書き込んでいる。速度は毎回変わるし、精度もあまりないので正確じゃないけど。

512バイトを書いた時で60kByte/sec、5120byte書いた時で540kByte/secくらい出てる。大容量書いてもあんまり時間が変わらない。1024byteから512byte増えるごとに0.25msecくらい増えていく。



4KiBを書いた時のキャプチャがこんな感じ。マルチブロック書き込みで8ブロック分を書き込んでいる。その後のシングルブロック書き込み複数個でFATを操作しているっぽい。
マルチブロック書き込みでは最初の1ブロックを書いたあとにちょっと長いビジーが発生し、その後のビジーはあまり長くない。

SPIの速度を変更する

STM32F1のSPIはCR1のBRに設定する。これはStdPerphLibではSPI_Initのみで変更できる。ということで自前で関数を作る必要がある。
static uint16_t SPI_Baud = 0;

static void SPI_BaudLow(void) {
    SPI_Baud = SPI1->CR1 & 0x0038;

    SPI_Cmd(SPI1, DISABLE);
    SPI1->CR1 = (SPI1->CR1 & 0xFFC7) | 0x0038;
    SPI_Cmd(SPI1, ENABLE);
}

static void SPI_BaudHigh(void) {
    SPI_Cmd(SPI1, DISABLE);
    SPI1->CR1 = (SPI1->CR1 & 0xFFC7) | SPI_Baud;
    SPI_Cmd(SPI1, ENABLE);
}

SPI_BaudLowではSPIを最低の速度で駆動する。72MHzなら281.25kHzとなる。またSPI_Baudに速度をバックアップする。
SPI_BaudHighではバックアップされたボーレートをレジスタに書き戻す。これはSPI_Initで設定した値になる。またSPI_BaudLowを呼ばれていない場合はSPI_Baudには0が入っており、これは分周比2となる。

それとボーレートの設定は通信中に変更してはいけないらしいのでSPI_Cmdでペリフェラルを終了させている。
ちなみにSPI_Cmdの中ではCR1のSPEを変更するだけなので、本来のSPI停止方法ではない。STM32F1のリファレンスマニュアルによると、RXバッファに値が入るまで待つ→RXバッファを読む→TXバッファが空になるまで待つ→ビジー状態でなくなるまで待つ→SPIを無効にする、という手順を踏む必要がある。ただこの方法は結構手間なのと、そもそも通信中にSPIを停止させるための手段なので、非通信時に変更したいのでSPI_Cmdだけで問題ないと判断した。

SDカードをSPIで使用するには、初期化時には100-400kbaudにする必要があるらしい。とりあえず36MHzでも問題なく初期化できているが、どうせ初期化するには200msecほどかかるので280kbaudでも36Mbaudでも大して変わりはない。

2015年12月22日火曜日

ZEROPLUS-LAP-Cでパケットのデータを表示する

LAP-Cは結構前から使っていたけど、パケットのデータを表示する機能は使ったことがなかった。



表示するにはWindowメニューのBus Packet Listをクリックすればいい。そうすると1トランザクションを一つのパケットとして表示する。複数のバスがある場合はバスごとに別れて表示される。データ量が多くなると右端で改行して次の行に表示されるようになる。データの位置(1始まりのカウント)はマウスオーバーをすると表示されるが、いまいち使いやすくない。16進数以外にもSetting...からASCIIや10進を選んで表示することができる。
I2Cのセンサを使っていた時にいちいち波形に表示されるデータを読んでいたが、Bus Packet Listなら一連のデータとして表示されるのでこっちのほうが簡単。

ところでLAPのソフトには幾つかのバージョンがあって、現在は3.12.xxが最新だと思う。しかし12系ではパケットキャプチャがインストールされない。そのため新規でLAPのソフトをインストールする場合は3.11系をインストールした後に、12系にアップデートした方がいい。過去のバージョンはメーカーのWebページからダウンロードすることができる。しかし台湾の方にあるサーバーで1個が50MB以上あるので、結構時間がかかる。いざ使いたい時に慌てないように。
UARTやSPIやI2Cは11系に無料で使えるキャプチャが入っているので、電子工作をする上では必須。他にもいろいろなパケットに対応していて、CANもあるのだが、大抵は有料で「ライセンス買ってね!」と表示されてぐぬぬ…となっているが。

プログラムの不具合はソースコードの中からおかしいところを探すほうがいいのだが、自分で書いたコードに入っている不具合は自分で発見することはかなり難しい。個人で開発する上ではロジック・アナライザは必須な機材であろう。

DMAでFatFs









上から
1) ソフトウェアでファイル読み込み
2) DMAでファイル読み込み
3) ソフトウェアでファイル書き込み
4) DMAでファイル書き込み
です。

1がおよそ11msec、2がおよそ3.5msec、3がおよそ22msec、4がおよそ12msec、という感じです。読み込みでは3倍、書き込みでは1.8倍 くらいでしょうか。書き込みではあまり高速化していませんが、1ブロック書き込むごとにビジーが2.7msecほどあって、これが2回あるので、ビジー待機だけで5msec以上になります。このビジー待機はおよそ1回の書き込みごとに1回の待機となりますから、1回に数kbyteを書くのも512byteを書くのも同じ程度の時間になります。つまり書き込みは一度に大量に書くほど早くなります。


ということでSDカードの書き込み動作もできました。あとはマルチブロックR/Wを作れば大容量を転送した場合のデータレートを計測できるようになります。
エラーチェックはほとんどやっていないし、挿抜検出も行っていないので実用には程遠いですが。
それと本来の目的はDMAによるバックグラウンド書き込みをやりたいので、スタート地点にも立っていないといえますが。


ところで、今回検証に使用しているメモリはTranscendの32GB/C10のmicroSDです。すぐ見つかる場所にあったのがこのカードだけなので、他のメーカーとの比較は行っていません。個人的には東芝のカードが早そうな気がするので試したいのですが、何かのついでに購入したあとになります。

STM32F1のSPIをDMAで双方向

STM32F1のSPI1を分周比2(バスクロック36MHz)で双方向通信した時のキャプチャです。



2バイトか3バイト程度を送ったところで少し隙間が空いてしまいます。これはDMAの帯域が足りないために、転送に遅延が発生していると推測できます。

F1のSPIはDMA1_3にTXが、DMA1_2にRXが設定されており、双方向で通信する場合にはDMA1の2chを使用することになります。36Mbit/secは4.5Mbyte/secですから、それが双方向で9Mbyte/secとなります。
一方、DMAは1転送に8クロック必要らしいので、72MHz動作では9Mbyte/secが限界となります。あれ、帯域足りてるじゃん。

おそらく1chだけを転送する場合は9Mbyte/secがフルに出るのですが、複数の転送を行う場合は次のデータを選ぶための比較が発生するためにフルで使用することができないのでしょう。

気になるデータの取りこぼしですが、先に書いたとおりSPIのTXが3、RXが2で、DMAは数字の小さいほうが優先されますから、受信のほうが優先度が高くなります。そのため受信データが転送されていない場合は送信が一時的に停止されるはずです。

ということで、「SPIをDMA転送したらきっちりSPI帯域使い切れるぜ!」と思ったらDMAの帯域が足りないというオチでした。それでもソフトウェアでフラグをポーリングして1バイトずつ送るよりは早いんですが。
もちろん送信だけの一方向通信なら受信データを読む必要はありませんから、SDカードへデータを書く場合は帯域をフルに使えます。バス速度的には 書き込み速度>読み込み速度 というちょっと不思議な感じになりそうです。

STM32F1のSPIをDMAで転送

今回は送信だけ、と受信だけ、の処理です。全二重の送受信処理は以下の関数を変形するだけで作れます。

送信は一方的に送りつけるだけなので楽ですが、受信する場合はデータを送信する必要がなくても送信用のDMAを設定する必要があります。何もデータを送る必要が無いのに大容量のバッファを確保して初期化するのも馬鹿らしいので、1バイトの配列に初期値(0xFF)を設定し、MemoryIncをDisableにして送信してやります。

それから転送が終わったら責任をもってDMA関連をDisableにしてやりましょう。さもないと次にSPIで通信を行った時に謎の動作をします(謎というか、ハングアップするだけですが)。

ソフトSPIとハードSPI

STM32F1のSPI1にmicroSDソケットを接続し、FatFsで読みだしてみました。分周比は2でバスクロックは36MHzです。





この画像はSDカードのシングルブロックリードコマンドの時のキャプチャです。上がDMA使用、下がソフトウェアで通信しています。
おおよその目安ですが、ソフトウェアでは1ブロックを読むのに1.8msec程必要です。対してDMAでは1ブロックの読み込みに460-600usec程度です。およそ3倍の速度差があります。前回のエントリではDMAとソフトでは10倍ほどの差がありましたが、今回はSDカードの内部処理待ちが含まれるために差が少なくなったのだと思われます。

今回はシングルブロックリードですが、マルチブロックでのR/Wであればだいぶパフォーマンスが向上すると思います。まだSDカードの書き込みコマンドや、マルチブロックの処理のコマンドは作っていないので、そのあたりを作ったらもう一度比較してみたいと思います。

2015年12月21日月曜日

STM32F1のSPI

STM32F1のStdPeriphDriverでは以下のコードでSPIの送受信と送信が可能です。

void SPI_WR(uint8_t *buff, uint16_t len) {
    do {
        while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
        SPI_I2S_SendData(SPI1, *buff);

        while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
        *buff = SPI_I2S_ReceiveData(SPI1);

        buff++;
    } while (--len);

    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
}

void SPI_W(const uint8_t*buff, uint16_t len) {
    do {
        while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
        SPI_I2S_SendData(SPI1, *buff++);
    } while (--len);

    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
}

SPI_WR関数ではバッファのデータを送信しながら、受信したデータをバッファに保存します(与えた配列を破壊します)。
データを受信する必要が無い(送信だけすればいい)という場合はSPI_W関数を使用すれば受信処理を省くことができます。


ということでこのコードを使って36MHzで初期化したSPIにデータを送ってみましょう。



左がSPI_WRで右がSPI_Wです。送信専用では多少はマシとはいえ、バスの速度を十分に使用していません。これはソフトウェアでポーリングしているためで、バス帯域をフルに使用するにはハードウェアで送信する必要があります(DMAで転送する方法はこちらのエントリで)。

センサのデータを読み取りたいなど、数MHzで10バイト未満程度ならあまり問題になることはないかもしれませんが(*)、SDカードにアクセスする場合はこのバス使用率は問題です。DMAでハードウェア化した場合、受信では10倍程、送信でも4倍程の高速化が望めます。

*) 数バイト程度ならDMA初期化のオーバーヘッドがあるのでソフトウェアのほうが早い場合もあるので、各環境で用途に合わせて確認する必要があります。

/* 追記 */

SPIの転送にDMAを使うと右端のようになる。


左端のソフトウェアWR、中央のソフトウェアWに比べ、右端のハードウェアWではクロックの間に隙間がなく、バスの使用率はかなり高い。ただしCSをアサーとしてから最初のデータが送信されるまでにしばらく時間がかかっている。これはDMAの初期化を行っているため。でも2分周で10バイトくらい送るならDMAのほうが早そう。

void SPI_DMA_W(const uint8_t*buff, uint16_t len) {
    DMA_InitTypeDef DMA_InitStructure;

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&SPI1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(buff);
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = len;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    DMA_DeInit(DMA1_Channel3);
    DMA_Init(DMA1_Channel3, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel3, ENABLE);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);

    while (DMA_GetCurrDataCounter(DMA1_Channel3));
    while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
}

他に予めRCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);でDMAのクロックを有効にしておく必要がある。

2015年12月11日金曜日

Cesiumで視点設定


Cesiumで視点を設定してみる。今回はとりあえず大樹町多目的航空公園の滑走路を指定した。


ドキュメントに書いてあるそのままが使える(Exampleの2. Using HeadingPitchRange offset)。
centerには注視点を設定する。滑走路上には垂直な線を1本と、高度500mあたりにラベルを書いている。今回の注視点はLabelの位置。
headinには視線の方向を、pitchには視線の仰角を指定する。多目的航空公園の滑走路方位は73度くらい(Google Earthによる実測)なので、今回はその角度を指定した。滑走路に垂直に立っている線が綺麗に滑走路の中心に見えるので角度の指定はおおよそ正しいようだ。それからpitchは-45を指定することにより斜め上から見下ろす感じになる。
あとrangeには視点から注視点の距離を指定する。今回は2500なので、注視点から半径2.5kmの球の表面に視点が存在するようになるはず。

それとlockAtでcenterを指定するとそこに注視点が固定されるみたい。なのでマウスで操作しても注視点から移動することができない。ズームはできるのでレンジの変更はできる。それからShiftを押しながらだと注視点を画面上の任意の位置に移動できる模様。

Cesiumはグラフィックリソースの要求が凄いのと日本語情報があんまりないのが欠点だが、JavaScriptでいろいろ叩けるので楽しい。
あと線量マップとか半減期とか原子時計とかいっぱい出てくるのでもうちょっと違う名前つけて欲しかった。

2015年12月6日日曜日

再配布可能パッケージの0x80240017エラー

Apacheを使おうと思って、とりあえずApacheを解凍してみたが、DLLが見つからない旨のエラーが出た。ということでWeb上の手順に従いVS2012C++の再配布可能パッケージを入れたが、それでもまだDLLが見つからない。どうやら2012ではダメっぽい。ということでVS2015C++の再配布可能パッケージをインストールしようとしたところ、0x80240017エラーが発生。

そもそもApacheを入れようとしたのはVirtualBoxにクリーンインストールしたWin7x64で、ほぼOSを入れただけの状態。そしてVS15C++のシステム要件には「Win7SP1」と書いてある。つまりService Pack未適用のWin7ではダメ。Windows UpdateでSP1を適用するとあっさりとインストールが進む。もちろんApacheも起動するようになる。

とりあえずソフトウェアを入れる時は必須環境のバージョンとかを確認するように、という教訓。

せっかくOSを入れるならUbuntuとか突っ込んでおけばよかったかなとか思ったり。

***

それと、VirtualBoxでWin7x64を入れる際にもいろいろと不具合が起こった。というのもOSの選択肢に64bitが出てこないのだ。BIOSで仮想化に使用する機能を有効化しないとダメらしい。このマシンを前のOSで使用した時に設定は変えたはずなのだけど、戻ってしまったのかと思ってBIOSを確認。MBはB85PLUSなのでBIOSではなくUEFIなのだけど、Intel Virtualization Technologyを確認したところ、ちゃんと"有効"に設定されている。
そして次に確認したのがWindowsの機能の"Hyper-V"というモノ。Windowsの機能の有効化または無効化の中にあるHyper-Vのチェックを外して再起動。
コレでようやく64bitが選択肢に出てきたので、それを選んでWin7x64をインストール。
しかしここでもまたつまずいた。取るべき選択肢が表示されているがいまいち理解しづらい。
これについてはVirtualBoxの"仮想マシン"→"設定"の"システム"カテゴリ"マザーボード"タブの"I/O APICを有効化"のチェックを入れることにより対応。本来64bitを選択するとこのオプションにチェックが入っているのだが、今回は選択肢が無かったために32bitで設定を作成してから、後でOSを64bitに変更した。そのためにチェックが外れていた模様。


ということでいろんな障害を超えながらようやく最低限の機能が使えるようになってきた。
しばらくはHTMLで遊ぶことになりそうだ。

2015年11月27日金曜日

C#でatanhとか

地形とか扱ってるとATANHとか出てくる。が、C#のMathにはatanhとかは入ってない。ということで自前で用意する必要がある。MSDNにいろいろ書いてあるのでそれを使う。

Derived Math Functions (Visual Basic)

C#で使うとこんな感じ

double Atanh(double x)
{
    return (Math.Log((1 + x) / (1 - x)) / 2);
}

他の関数も同様に使用可能。

2015年11月18日水曜日

STM32F1 タイマの同期

STM32F1では、それ以外のマイコンと同じように複数のタイマを使用することができる。そして複数のタイマを同期して使用することができる。この同期とは、例えばTIM2のPWM立ち上がりエッジでTIM3を開始する、といったことが可能になる。

今回はTIM3のエッジでTIM2を開始するための方法。

まずはTIM3とTIM2を適切な設定で初期化する。今回はTIM3のOC2を使用するので、それらの設定も必要。

次にTIM3をマスタとしてトリガを出力できるようにする。
TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_OC2Ref);

そしてTIM2をスレーブとして設定する。
TIM_SelectInputTrigger(TIM2, TIM_TS_ITR2);
TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Trigger);

TIM_TRGOSource_OC2RefはOC2をトリガ出力として使用するための設定。TIM_TS_ITR2はITR(内部トリガ)2をソースとして使用する。TIM_SlaveMode_Triggerはタイマをスレーブモードとするための設定。

それと今回はTIM2を TIM_SelectOnePulseMode(TIM2, TIM_OPMode_Single) でワンパルスモードに設定した。これはTIM3のトリガでTIM2を1回だけ動作させるため。

あとはTIM_Cmdで開始すればマスタが出力したトリガでスレーブが動作する。マスタのトリガやスレーブの設定はRM0008を参照すれば良い。ただしStdPeriphLibはヘッダファイルを読んでも何が何だかわからないのが難点。RM0008で欲しい機能を探して、StdLibのソースを読んで、それっぽい関数を探す、という作業が必要。


それから、OnePulseModeでPWMを出力したい場合、大抵は望んだ動作をしないはず。ワンパルスモードでは「PWMを1回出力して、ポートを非アクティブ化した状態で終了する」ということを望んでいると思うが、そうはならない。というのは、OPMでは「タイマ更新イベントでタイマを停止する」という動作になっているから。タイマ更新イベントでは「ポートをアクティブ化する」と、「タイマを停止する」が同時に行われる。
本当はもっとスマートな処理方法があるはずなんだけど、まだ見つけられていない。OCをPWM1で初期化した場合、カウンタがしきい値を超えたら停止する みたいな動作がほしいんだけど。

ストロボスコープ

ストロボスコープを作っている。ストロボスコープとは高輝度の明かりを一定間隔で照射するためのモノで、常に運動している物体を止めて撮影するのに使用される。ストロボの発光間隔は既知となるから、物体が映った時の位置がわかれば、距離と時間から速度を求めることができる。


まだ途中までしか作りこんでいないのでインターバルでの発光しかできないが、動作確認程度はできるようになってきた。


スロープ上で球体を転がした時の写真。左から転がり始め、右に進むに従って速度が上がっていることがわかる。

もう少し手直しが必要だが、外部トリガに対応するとフォトインタラプタとかを使うことにより物体が通過した瞬間に撮影することができるようになる。これができればミルククラウンとかを簡単に撮影できるようになる。

それとこの基板はNchFETをドライブしているだけだから、出力はLow,HiZの2値となる。これは単純なレリーズ端子がついてるカメラをドライブすることができるので、インターバル撮影のタイマとしても使用できる。バルブモードにしておけばパルス幅でシャッター速度を変更できるから、うまく制御ソフトを作ればいろいろと面白い表現ができるかも。

録画のソート

パナのレコーダは録画の並べ替えができないみたい。
DMR-BRW1000のFAQは出てこなかったけど、基本的に変わらないはず。

Media Centerの場合は番組名(放送時のタイトル)と録画日付のソートのみ対応だけど、録画データ自体はWinのエクスプローラで表示できるので、ファイル名を変更すれば自然順ソートで表示することができた。

DMR-BRW1000はLAN経由でWebブラウザから録画一覧を見ることができるので、HTMLを解析すればリストを取り出せられそうだけど、有線LANで繋いでる割にかなり遅い。
せっかく無線LANや有線LANを搭載して、CPUも高性能な奴積んでるんだからTCP/IPとかで接続できたら楽しいのに。

IoTってメーカーお仕着せの「おれのかんがえたさいきょうの(ry」とかではなく、「こういうデータがあるから使ってみて!」のほうが楽しいと思うんだけど。

2015年11月14日土曜日

自動録画

パナのDIGAはdimoraというWebサイト経由で録画の設定をすることが可能です。

自動録画予約は10組を登録でき、1組あたり10個のキーワードと5個の除外キーワードを設定できます。Media Centerのキーワード録画と比較して 1)キーワード数が少ない 2)キーワードが反映されるまでに時間が掛かる という問題があります。「番組表を見て気になったキーワードを片っ端から追加する」といった使い方はできません。


自動録画予約はサーバー上で番組表から検索を行い、放送日が近づいたらレコーダへ転送する という動作をしているような気がします。そのためレコーダの処理能力とは関係なくキーワードを登録可能なはずですが、実際にはそのような実装にはなっていません。また自宅にはモバイルルータしか無く、外出中はオフラインになる という場合には自動録画はされないため、長期間家を開けるときは何らかの対策が必要となります。

キーワードのAND,OR,NOTは「必須 キーワード:」の右にある疑問符をクリックすることにより優先度等の設定を見ることができます(AND AND OR ANDのような設定は書いてませんが)。


現在のところこの機能について大きな不満はありません。しかし上記の通り常にオンラインである必要がある、キーワード数が余り多くない、といった不満はあります。またキーワードには正規表現やワイルドカードが使用できればいいなとも思います。

2015年11月13日金曜日

PanasonicのDIGAを買った

先日、パナのHDDレコーダ、DIGA(DMR-BRW1000)を買いました。少し前の機種ですが、1)最新機種に欲しい機能がなかった 2)旧機種の在庫なので若干(5%程度)やすかった ということで。

旧機種ですが、おおまかな機能は大きく変化することはないと思うので、機能のレビューとか細々と書いていこうと思います。

今まで使っていたのはWin7のMedia Centerで、それと比較した感じになると思います。

C#でスクリーンキーボード

ナショジオのAH-64組み立て番組見てたらキーボードが面白そうだったのでC#で作ってみた(ソフトウェアキーボード)。


もちろん普通にスクリーンキーボードとして使用可能である(といっても、IME切り替えキーは無いので英語圏の入力のみだけど)。AH-64って2人乗りとはいえ、飛んでる間に左手で長文を入力するとも思えないし、ちょっとした情報の入力程度にしか使わないんだろうけど、入力しやすいとは思えないなぁ。

一応形だけは作ったけど、例えばZの横にあるスラッシュと、Enterの上にある除算記号は同一の文字が入力される。また±キーは本来であれば入力値の正負が反転されるはずなんだけど、Winではそれっぽいキーは無い気がするので今のところ何も割り当てられていない。CLRはKeys.Clearを割り当てている。ただnotepad.exeではClearは何も行わないみたい。

スクリーンキーボードは大きさ固定のユーザーコントロールとして作成している。そのため他のプロジェクトに貼り付けることも可能。各キーはButtonを継承したコントロールを使用している(円形表示や四角の枠を表示するため)。ボタン間の分割ラインはLabelのAutoSizeをFalseにしたうえで使用している。他にはボタンが押されたらそのボタンに応じてKeysをEventで渡せるようにしている。

スクリーンキーボードとして使用するには、1)スクリーンキーボードが押されてもアクティブにならない 2)キーを送信する という動作が必要になる。今回は .netで何かつくろう スクリーンキーボードの作成 を参考にした。キーを送信するにはkeybd_eventを使用しているが、C#にはSendKeyというクラスがある。もっとも、keybd_eventならKeysをbyteにキャストするだけで良いのでDLLを使ったほうが簡単だと思う。

Formを常に最前面に表示するにはTopMostをTrueにすればいいらしい。が、僕の環境ではこれだけではダメだった。「デザイナのプロパティでTopMostをTrueにする」かつ、「LoadイベントでTopMostにtrueを入れる」という操作が必要だった。どちらか片方ではダメで、LoadではなくFormのコンストラクタで代入してもダメだった。ということで今回はデザイナでTrueにしつつ、コンストラクタでLoadに匿名メソッドを追加することで設定した。

マウスで操作する上ではスクリーンキーボードは大して便利ではないし、ハードウェアキーボードがあるなら選択肢にもならないが、タッチパネルなPCなら、ちょっとした入力程度ならそこそこ便利。ウチにあるタッチパネルはHDMI/USB接続の液晶だけなので部屋でしか使えないし、それならハードウェアキーがあるから、とりあえずは不要かな。

2015年11月3日火曜日

日本のSF小説における軍隊の階級

Wikipediaの"日本のSF小説"カテゴリに含まれる小説に登場する軍隊の階級を調べてみた。

条件
1) 作品ページに書かれた登場人物のみを扱う
2) 階級は24種類
3) 作品はSFカテのみ

1) Wikipediaでは登場人物が増えると別ページに人物の一覧を書いている。そのため作品ページのみの参照では取りこぼしが発生する可能性が高い。その他に、単純に文字列一致でカウントしているため、「階級の説明」など、登場人物とは関係ない数をカウントしている可能性がある。

2) 階級は 大元帥,元帥,上級大将,大将,中将,少将,准将,代将,上級大佐,大佐,中佐,少佐,上級大尉,大尉,中尉,少尉,准尉,曹長,軍曹,伍長,兵長,上等兵,一等兵,二等兵 の24種類を使用した。この分類は軍隊の階級の一番上にある表から拾ってきた。また自衛隊は階級の名称が違うからカウントされていない。

3) コレが一番重要。例えば最近までアニメ放送されていた"ゲート"はSFカテではなくファンタジーカテに属しているため、この集計対象には含まれない。

作品総数は現時点で297作品がカテゴリに属していた。カテゴリに属しているページのURLをすべて取り出し、そのページのHTMLをダウンロード後、ID"content"内にあるテキストからそれぞれの階級を文字列一致でカウントした。

すべての作品に登場するそれぞれの階級の合計。一番多いのが大佐で106人、ついで大尉の82人。


作品別の階級ヒット数。"宇宙一の無責任男"は桁が違うので省略した。二番目に多いのは"魔法科高校の劣等生"で42件だった。297件の内212件はヒット数が0だった。


ただ数字を拾ってきただけなのであまり面白くない…かな。今回は重複のチェックとかが面倒なので1つのカテだけを使用した。他のカテも含めれば面白いかも?

今回はSF小説カテだったが、次はアニメ作品を調べてみたい。しかしWikipediaにはアニメ作品一覧ページと言うのは無いみたい。あかさたな別でページが分かれてるので、そこから拾ってくる必要があるかな。ちょっと面倒。


*まとめ
軍人がいっぱい出てくる(かも知れない)小説を読みたいなら魔法科高校を読めばいいと思うよ!

*最後に
現時点での階級がヒットする作品名とヒット数一覧をCSVで貼り付けておく

2015年10月22日木曜日

STM32F1の旧ライブラリでI2C通信

STM32F1の旧ライブラリ(StdPeriph_Driver)でI2C通信を行うための関数を作りなおした。

宣言
int I2CTR(I2C_TypeDef* I2Cx, uint8_t Addr, uint8_t*Buff, uint8_t TxLen, uint8_t RxLen)

I2Cx
通信に使用するポート

Addr
デバイスアドレスを使用する 7bit形式ではなく8bit形式で渡す

Buff
送受信のバッファを渡す

TxLen
送信データ数を渡す

RxLen
受信データ数を渡す

この関数では送信バッファと受信バッファを兼用している。そのためレジスタアドレスを指定する引数は取らない。レジスタアドレスを指定したい場合はBuff[0]にアドレスを指定し、TxLenを1にする。ROMのようなレジスタアドレスが複数バイトになる場合はBuffに必要なバイト数を設定し、TxLenにその長さを渡す。
送信だけで、受信する必要が無い場合はRxLenを0にすればいい。RxLenが0の場合は送信が終了した段階でStopを発行し関数を終了する。
手持ちのデバイスの中には「レジスタアドレスを指定せずに読みだす」という物がないため、TxLen==0の場合の動作確認はしていない。だけどたぶん失敗すると思う。

const int timeoutは無限ループに入らないようにしている。ただ0x7FFFFFFFでは明らかに長過ぎるから、もっと短い値に変更しないと意味が無い。例を挙げると72MHzで0xFFFFを指定すると67msecほどでタイムアウトする。大雑把な目安としてだいたい1マイクロ秒くらいの分解能になる。例えば10000を指定すると10msecくらいでタイムアウトするので、5000とか10000くらいがいいと思う。
タイムアウトは1回のループ毎に変数をtimeoutで初期化し、whileで比較、デクリメントを行っているだけ。そのためマイコンの動作クロックが変わると待ち時間も変化する。

2015年10月20日火曜日

ISSを撮ってきた

VIXENのA80Mfという鏡筒にK-5をつけてISS(国際宇宙ステーション)を撮ってみました。天体望遠鏡の焦点距離でもものすごいトリミングしないと点にしか見えませんが。それにしても500kmも離れた物体を形がわかるレベルに撮影できるとは驚き。




撮影に使用したのはVIXENのA80Mfという鏡筒と経緯台と三脚のセットです。値段は4万円台前半でありながら、ガッチリした三脚と微動雲台、それに910mmの単焦点レンズが付属します(もちろん接眼レンズ等の周辺機材も)。コスパがヤバイです。カメラ用でこのクラスの三脚や雲台を探したらそれぞれにA80Mfと同じくらい掛かりそうな気がします。レンズに至っては桁が違うんじゃないかと。もっともA80Mfは単純な光学系ですから、色収差はかなりあります。でもこの値段なら気にならないですよね!

今回は直上通過直後に地球の影に入るという素晴らしいタイミングのパスに恵まれ、望遠鏡を使うのは2回目ですがかなり綺麗に撮ることができました。ISSはバカみたいにデカく、簡単に撮影することができます。みんなもガンガン撮ればいいと思うよ!

2015年10月6日火曜日

AMトランスミッタを作ってみた

STM32F103VE(STBee)でAMトランスミッタを作ってみました。
市販品はFMトランスミッタですが、FMラジオの周波数帯をマイコンで扱うのは辛い、FM変調を作るのは更に辛い、等の理由によりAMとしました。もっとも、変調はAMですが周波数帯は中波ではなく長波なので、市販のAMラジオでは聞けないと思います。今回はIC-R6を使って動作確認をしました。



画面右下にあるのが受信に使用したIC-R6で、音声出力がカメラの音声入力に入っています。画面中央部にあるプローブが刺さったボードがSTBeeで、これがマイク入力とRF出力を行っています。マイクは基盤右上の黒い丸の部分です。基盤左下から右上に向かっている赤い線が空中線代わりのジャンパワイヤです。送信出力が非常に低いため、この距離でもスケルチがぎりぎり開く程度で、撮影の時はIC-R6のアンテナとトランスミッタのアンテナを接触させています(被覆で絶縁されてますが)。

(こういうブロック図を簡単に作る方法って何かないかな)

今回は100kHz(0.100MHz)のAM波を送信するため、正位相・逆位相を出力するために2本のDACを使用しました。これはTIM3のタイミングでDMA転送し、DMAのHTとTCで割り込みを発生させ、その割り込みでマイクのバッファに入っているアナログデータを生成します。
図中ではTIM3から割り込みが発生してる風ですが、実際にはDMAから発生させています。


音質もラジオで衰退してる割には近距離であれば問題無いですし、電波で遊びたいという欲求はかなり解消できたかな、と思っています。100kHzではアンテナの利得は無いも同然で、送受信共に感度が低いので長距離通信はおろか、1mも離したらスケルチが閉じっぱなしになってしまいますが。
現行法に対応した形で簡単に無線で遊ぶのはコレが限界かな、とも思います(コレも厳密にはどうかわかりませんが)。

2015年9月29日火曜日

GPIOを操作するマクロを作る

久しぶりにSTM32F1で遊んでいます。
雛形を作りなおしているので、そのついでにGPIOを操作するマクロを作ってみました。
基本的にSTM32F1用。

マクロ

#ifndef __hardware_H
#define __hardware_H

#include "includes.h"

#define     HW(name, x, type)       name##_##x##_##type
#define     HW_OUT(name, x, val)    GPIO_WriteBit(HW(name, x, PORT), HW(name, x, PIN), (val) ? HW(name, x, ACTIVE) : !HW(name, x, ACTIVE))
#define     HW_IN(name, x)          (GPIO_ReadInputDataBit(HW(name, x, PORT), HW(name, x, PIN)) == HW(name, x, ACTIVE))
#define     HW_OT(name, x)          (GPIO_ReadOutputDataBit(HW(name, x, PORT), HW(name, x, PIN)) == HW(name, x, ACTIVE))
#define     HW_TGL(name, x)         HW_OUT(name, x, !HW_OT(name, x));

#define     LED_1_RCC       RCC_APB2Periph_GPIOD
#define     LED_1_PORT      GPIOD
#define     LED_1_PIN       GPIO_Pin_4
#define     LED_1_ACTIVE    0

#define     SW_1_RCC        RCC_APB2Periph_GPIOA
#define     SW_1_PORT       GPIOA
#define     SW_1_PIN        GPIO_Pin_0
#define     SW_1_ACTIVE     1

#endif /* __hardware_H */
LED1はアクティブLOW接続でResetで点灯、Setで消灯。
SW1はアクティブHIGH接続で押された時にSet。

使用例

void LED_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(HW(LED, 1, RCC), ENABLE);

    GPIO_InitStructure.GPIO_Pin = HW(LED, 1, PIN);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(HW(LED, 1, PORT), &GPIO_InitStructure);
}

void SW_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(HW(SW, 1, RCC), ENABLE);

    GPIO_InitStructure.GPIO_Pin = HW(SW, 1, PIN);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_Init(HW(SW, 1, PORT), &GPIO_InitStructure);
}

スイッチを読む場合
if (HW_IN(SW, 1)) { ... } else { ... }

LEDを点灯させる場合
HW_OUT(LED, 1, 1);

LEDを消灯させる場合
HW_OUT(LED, 1, 0);

LEDを切り替える場合
HW_TGL(LED, 1);

もちろんスイッチやLED以外にも使用可能です。
ほとんど便利じゃないし、forで回したりできないので、ベタ書きとほとんど変わらないですが。
あとUnity(テストハーネス)使ってる人はわかると思いますが、マクロでごにょごにょやってるとエラった時にメッセージが非常に難解です。
今回はHWを接頭辞として使っていますが、LED(1, PORT)やSW(1, PIN)のほうが直感的な気がします。

2015年9月23日水曜日

C#で音声合成出力

C#で音声合成出力を試してみた。OSはWin10、環境はVS2015WinDesktopのC#を使った。
参照にSystem.Speechを加える必要がある。

using System;
using System.Speech.Synthesis;
using System.Windows.Forms;

namespace Speech
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            using (SpeechSynthesizer syn = new SpeechSynthesizer())
            {
                syn.SelectVoice("Microsoft Zira Desktop");
                syn.Speak(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));
            }
        }
    }
}

今回はSelectVoiceでZira(en-US)を指定した。デフォルトはHanako(ja-JP)だが、日本語だとイントネーションが不思議な感じになる。やはり英語のほうが自然。
それとSpeakで音声を出力している間はスレッドをブロックしてしまうので、実際に使うなら何らかの工夫が必要だと思う。
読み上げる速度はOSの設定から「音声認識のプロパティ」ダイアログ内「音声合成」タブにある「音声の速度」で設定できる。最速だと何を言ってるかわからないし、最遅でも何を言ってるかわからない。あんまり速度の調整はやらないほうが良さそう。

2015年9月19日土曜日

XNAでマイク入力

XNAでマイク入力を試してみた。
XNAは開発が終了してしまったが、まだDLは可能(なはず)。XNAをインストールするときにはVisual Studio C# 2010が必要になるみたいなのでそれを先に入れる。一旦インストールしてしまえば、ライブラリはVisual Studio 2015のC#からも使用可能。XNAは音声(WAVE)IOやXbox360コントローラなど、C#では珍しくハードウェアアクセスが豊富なので入れておくと便利。

さて、マイク入力だが、今回はスレッドを1本作って無限ループとした。ただ作ってから気がついたけどタイマとかで定期的に読めば問題なさそう。今回試した環境では16bit1chのデータが読めた。リファレンスによると8bit or 16bit / 1ch or 2chということなので、データ数からビット数やチャンネル数を予測するのは不可能っぽい。実際に使うには何らかの対策が必要そう。
それとVS2010がインストールされていたのがデスクトップだったので、そっちで開発をしているが、ノートにEXEを投げると正常に動作してくれないという問題がある。ノート側でビルドすれば動きそうな気もするけど、まだVS2010が入ってないので確認ができない(そもそもVS2015も入れてないが)。

音声出力も動作確認はしたが、また次の機会に。

/* 例:Microphone Mic = Microphone.Default; */
/* Microphone.Allですべてのデバイスを獲得できるのでそこから選んでも良い */

Mic.Start();

while (IsLoop)
{
    byte[] buffer = new byte[Mic.GetSampleSizeInBytes(TimeSpan.FromSeconds(0.1))];
    int bytesRead = 0;

    DateTime ReadTime = DateTime.Now;

    while (bytesRead < buffer.Length)
    { bytesRead += Mic.GetData(buffer, bytesRead, buffer.Length - bytesRead); Thread.Sleep(10); }

    short[,] ROW = new short[buffer.Length / 2, 1];

    for (int i = 0; i < buffer.Length; i += 2)
    { ROW[i / 2, 0] = BitConverter.ToInt16(buffer, i); }

    this.ROW = ROW;
}

Mic.Stop();

軽く動作の説明をすると、最初の方の0.1を渡すメソッドでバッファ数を決めている。これは0.1秒分のバッファサイズとなる。
次にMic.GetDataでデータを読む。これは引数にオフセットを渡せるので、バッファ全体を読み込むまでループする。データを纏めて読む必要が無い場合は1回に読める分だけ処理してもいい。
またGetDataのループの中ではThread.Sleep(10)で10ミリ秒のスリープを入れている。これはこのwhite(true)でCPUリソースを浪費しないようにするためだ。
そしてshort(符号付き16bit)の2次元配列を作る。今回は1ch16bitで決め打ちしているのでバッファの半分の長さで1ch分となっている。
あとはforで回してBitConverterでshortに戻している。これはビットシフトとかでもいいと思うが、BitConverterのほうが確実(エンディアンが一致している限りは)。
最後にthis.ROWにROWを代入している。コレとは別のループでthis.ROWを監視しておき、データが入ったら処理を行い、処理が終わったらnullを入れてデータがないことを示す という感じでマイク入力のWAVEデータを扱えばいい。

配列をBitmapにGraphicsのDrawLineでグラフにすれば生の波形を表示することができる。PCにステレオミキサが入ってればそれを録音することによりPCの音が表示されるので、iTunesとかてきとーな音源を見てみると面白い。

2015年9月14日月曜日

C#のQueueに配列を突っ込む

C#のQueue(System.Collections.Generic.Queue<T>)は可変長のFIFOとして使えるので、バッファとして使いやすい(コストは考えないことにする)。しかしQueueへ追加するにはEnqueueメソッドを使う必要があり、これは1要素ずつしか追加することができない。
ということで拡張メソッドで拡張してみた。

using System;
using System.Collections.Generic;

namespace QueueTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Queue<int> Q;

            Q = new Queue<int>();
            Q.Enqueue(1);
            Q.Enqueue(2);
            Q.Enqueue(3);
            Q.Enqueue(4);
            Q.Enqueue(5);
            while (Q.Count > 0)
            { Console.WriteLine(Q.Dequeue()); }

            Q = new Queue<int>();
            Q.Enqueue(new int[] { 1, 2, 3, 4, 5 });
            while (Q.Count > 0)
            { Console.WriteLine(Q.Dequeue()); }
        }
    }

    public static class MyExtensions
    {
        public static void Enqueue<T>(this Queue<T> Queue, T[] Datas)
        {
            for (int i = 0; i < Datas.Length; i++)
            { Queue.Enqueue(Datas[i]); }
        }
    }
}

動作としては与えられた要素数をforで追加するだけなので、わざわざ書かなくとも適時forで回せばいい。しかし拡張メソッドで作っておけば通常のEnqueueとおなじ感覚で使用することができる。もしもオーバーロードするのが嫌というならEnqueueRange<T>(this Queue<T>, T[] Datas) とでもしておけばいいだろう。

2015年9月13日日曜日

C#の拡張メソッド

C#には拡張メソッドという仕組みがあるらしいので、使ってみる。

using System;
using System.Text;

namespace MyExSample
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] Array = Console.ReadLine().Split(' ').ArrayToInts();
            Console.WriteLine(Array.ArrayToString());
        }
    }

    static public class MyExtensions
    {
        public static string ArrayToString(this int[] Array)
        {
            if (Array.Length == 0)
            { return (""); }

            StringBuilder sb = new StringBuilder();

            sb.Append(Array[0]);

            for (int i = 1; i < Array.Length; i++)
            { sb.Append(" "); sb.Append(Array[i]); }

            return (sb.ToString());
        }

        public static int[] ArrayToInts(this string[] Texts)
        {
            int N = Texts.Length;
            int[] A = new int[N];
            for (int i = 0; i < N; i++)
            { A[i] = int.Parse(Texts[i]); }
            return (A);
        }
    }
}
/*------*/
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace MyExSample.Tests
{
    [TestClass()]
    public class MyExtensionsTests
    {
        [TestMethod()]
        public void ArrayToIntsTest()
        {
            int[] Array;

            Array = new string[] { }.ArrayToInts();
            Assert.AreEqual(0, Array.Length);

            Array = new string[] { "1", "2", "3" }.ArrayToInts();
            Assert.AreEqual(3, Array.Length);
            Assert.AreEqual(1, Array[0]);
            Assert.AreEqual(2, Array[1]);
            Assert.AreEqual(3, Array[2]);
        }

        [TestMethod()]
        public void ArrayToStringTest()
        {
            Assert.AreEqual("", new int[] { }.ArrayToString());
            Assert.AreEqual("1 2 3", new int[] { 1, 2, 3 }.ArrayToString());
        }
    }
}

文字列の配列を整数の配列に変換するやつと、整数の配列を文字列に変換するやつ。エラー処理とかは一切していないので実際に使う場合はてきとーにごにょごにょと。

ところで、MsTestで配列をテストするのにいい方法は無いものか。forで回してエラーメッセージにIndexを入れておくとかかなぁ… それこそ拡張メソッドで実現するべきっぽいですよね。まぁMsTestが使いづらかろうともデフォルトで使える以上は使い続けますが。

2015年9月3日木曜日

OpenCvSharpでWebカメラを表示する

Webカメラというか、USBビデオクラス(UVC)を使用するUSBカメラを表示するためのサンプル。USBカメラを表示したかったが録画する機能とかもいらないしサクッと作ってみた。

OpenCvSharpで読み込んでC#のフォームに表示してもいいが、今回は手っ取り早くOCvで表示まで行った。

ソースコードはオブジェクト指向とは思えないような感じになってる。がまぁ動作してるので。。。

文字列入力を読み込みたかったので、スレッドでQueue<string>にConsole.ReadLineを読み込んでいる。一般的には「CvのWindowで何かキーを押せば終了」だと思うが、このコードはConsoleにquitを入力することで終了する。Console.ReadLineは同期読み込みだから、途中で読み込みを止めたいと思っても止められない(はず)。ということで最後にユーザーがEnterを入力してやる必要がある。Inのストリームを引っ張ってきて自前で処理すればいいかもしれないが、今回は面倒だったのでやってない。

解像度は「適切な(縦横比の)解像度で縦横を連続して設定すると適用される」みたいな動作になってるのでハードコードしてやった。

リアルタイムで映像を表示するだけの機能しかないが、画角の確認だとかフォーカスの確認くらいなら可能。



2015年9月2日水曜日

BCDを計算するメソッド

たまーに使われてる二進化十進表現(BCD)をC#で扱うためのメソッド。

二進化十進表現とは10進数の何桁かの数字を16進数で見やすくするためのエンコードの事。二進化というけど、2進数での表現ではあまり人間にはメリットがない。
そもそもBCDが便利ということはほとんど無く、実際に使うのは7セグLEDデコーダチップのインターフェースだったりとかで使われているだけ。他にはシリアルポートに特定のデータを流したくない場合にBCDでエンコードするとかもやるみたい。例えばスタートバイトをとストップバイトを0xF0以降に配置しておき、数値データはBCDで流した場合、16進表示のシリアルモニタで見るときに人間がわかりやすいし、BCDエンコードしてしまえば0x00-0x99の範囲しか扱わないのでストップバイトと競合することもない ということらしい。

本体
public class BCD
{
    static public UInt64 To(UInt64 HEX)
    {
        UInt64 val = 0;

        val |= (HEX % 10) << 4 * 0; HEX /= 10;
        val |= (HEX % 10) << 4 * 1; HEX /= 10;
        val |= (HEX % 10) << 4 * 2; HEX /= 10;
        val |= (HEX % 10) << 4 * 3; HEX /= 10;
        val |= (HEX % 10) << 4 * 4; HEX /= 10;
        val |= (HEX % 10) << 4 * 5; HEX /= 10;
        val |= (HEX % 10) << 4 * 6; HEX /= 10;
        val |= (HEX % 10) << 4 * 7; HEX /= 10;
        val |= (HEX % 10) << 4 * 8; HEX /= 10;
        val |= (HEX % 10) << 4 * 9; HEX /= 10;
        val |= (HEX % 10) << 4 * 10; HEX /= 10;
        val |= (HEX % 10) << 4 * 11; HEX /= 10;
        val |= (HEX % 10) << 4 * 12; HEX /= 10;
        val |= (HEX % 10) << 4 * 13; HEX /= 10;
        val |= (HEX % 10) << 4 * 14; HEX /= 10;
        val |= (HEX % 10) << 4 * 15;

        return (val);
    }

    static public UInt64 From(UInt64 BCD)
    {
        UInt64 val = 0;

        val += (BCD & 0xF) * 1; BCD >>= 4;
        val += (BCD & 0xF) * 10; BCD >>= 4;
        val += (BCD & 0xF) * 100; BCD >>= 4;
        val += (BCD & 0xF) * 1000; BCD >>= 4;
        val += (BCD & 0xF) * 10000; BCD >>= 4;
        val += (BCD & 0xF) * 100000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000; BCD >>= 4;
        val += (BCD & 0xF) * 10000000; BCD >>= 4;
        val += (BCD & 0xF) * 100000000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000000; BCD >>= 4;
        val += (BCD & 0xF) * 10000000000; BCD >>= 4;
        val += (BCD & 0xF) * 100000000000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000000000; BCD >>= 4;
        val += (BCD & 0xF) * 10000000000000; BCD >>= 4;
        val += (BCD & 0xF) * 100000000000000; BCD >>= 4;
        val += (BCD & 0xF) * 1000000000000000;

        return (val);
    }
}

単体テスト
[TestClass()]
public class BCDTests
{
    [TestMethod()]
    public void ToTest()
    {
        Assert.AreEqual(0x000000000000ul, BCD.To(0));
        Assert.AreEqual(0x000000000001ul, BCD.To(1));
        Assert.AreEqual(0x000000000009ul, BCD.To(9));
        Assert.AreEqual(0x000000000010ul, BCD.To(10));
        Assert.AreEqual(0x123456789012ul, BCD.To(123456789012));
        Assert.AreEqual(0x999999999999ul, BCD.To(999999999999));
    }

    [TestMethod()]
    public void FromTest()
    {
        Assert.AreEqual(0ul, BCD.From(0x0000000000000000));
        Assert.AreEqual(1ul, BCD.From(0x0000000000000001));
        Assert.AreEqual(9ul, BCD.From(0x0000000000000009));
        Assert.AreEqual(10ul, BCD.From(0x0000000000000010));
        Assert.AreEqual(123456789012ul, BCD.From(0x123456789012));
        Assert.AreEqual(999999999999ul, BCD.From(0x999999999999));
    }
}

Visual Studioの単体テストの本来の使い方をやっと(偶然)発見したのだけど、結構便利。あとどれくらい時間がかかったのかがわかるのでちょっとプレッシャーがかかる。Fromのテストでは1msec未満だが、Toのテストでは4msec以上かかっている。パフォーマンスの差が激しいのが不思議。無理やり解釈すれば、Toはコンパイラ最適化によってビット演算のみで計算され、Fromはビット演算以外に加算と乗算が発生しているため とか?

2015年8月26日水曜日

C#で熱画像っぽい色合いを作る その2

前回、IFとかで熱画像っぽい色を無理やり作って満足していたのだが、どうにも物足りなくなってきた。というのは、本家FLIRの熱画像が素晴らしかったからだ。

この画像は本家FLIRの熱画像のスケールをスペクトル解析したもの。赤、緑、青と、輝度が表示されている。白い輝度の線を見ればわかるが、綺麗に直線になっているのだ。つまり「輝度でモノクロ化」した場合に、熱画像が綺麗に白黒になる。これはカラーで作成した資料を白黒でコピーした場合に大きく力を発揮するだろう。


そして僕が作ったのがこちら。
前回よりは若干直線性が増していると思う。しかしまだ完全ではない。

次にこの色をつくるための計算式だ。

Rf = 0.05 + Math.Sin(Value * 1.8 - 0.05) * 0.9;
Gf = 0.5 + Math.Sin((Value + 1.4) * 3.2) * 0.4;
Bf = 0.5 + Math.Sin(Value * 1.6 - 1) * 0.7 + Math.Sin(Value * 6 + 0.4) * 0.5;

たったこれだけである。前回のIF処理のネストは何だったのか。でも三角関数を使えば三角波なども作れるようなので、三角関数の組み合わせで遊ぶのは結構楽しい。

上記の計算だが、赤と緑は1)オフセット、2)位相、3)周期、4)振幅の4個のパラメータで構成されている。青は上記に位相、周期、振幅がもう1セット加わった7個のパラメーターとなる。人間の手で調整するのは大変だが、目的の形をプログラムで判定することができれば総当りで最適な計算式を作れるかもしれない。特に直線性は総当りで調べて行ったほうが簡単に作れる気がする。ただ、直線性だけを見たのでは熱画像風に使えるとは限らないので、候補を幾つか作らせて人間が確認する、ということになるだろうが。

とにかく、今回わかったことは、IFなんて使わなくていい ということだ。パラメータを調整するだけで任意のスペクトルを作れるんだから、次はもうちょっと最適化した計算を報告できるようにしたい。

C#で熱画像っぽい色合いを作る



昔の熱画像風ではなく、FLIRの熱画像風の色合いになってる。また上半分は結構のっぺりした感じになってる。これは赤と緑が早々と飽和してしまうため。
ウインドウ内、上は0-1の熱画像風、下は各色の割合を表示している。また下のウインドウは上のそれをPaint.netで白黒化したものとなる。それとデコボコしながら右肩上がりになっているグラフはRGBを輝度に近似したグラフとなっている。デコボコして入るが、一応右肩上がりになっているので白黒化してもBk-Whで変化しているのがわかると思う。
この熱画像風化では内部でサインテーブルを使用している(Math.Sinを使用)。そのために計算コストはかなり高いものとなっている。また画像を見てもわかるとおりに青は綺麗に増加させているわけではないため、多数の浮動小数点演算が行われる。今回はPCでちょっと使いたかったのでこのようになっているが、マイコンで使うにはかなり大変だろう。

2015年8月22日土曜日

C#でWaveファイルを作る

C#でWAVEファイルを作りたくなったのでメモ。

ヘッダ部分はCの構造体みたいな感じにスマートに作れるっぽいけど、とりあえず今回は1個1個ちまちま書き込んでる。
Visual Studio 2015のC#6からプロパティの初期化を行えるようになってちょっと便利になった(コンストラクタ作れって話だが)。

このプログラムはFormアプリケーションとして動作するが、起動時にデスクトップに"test.wav"というファイルを作ったらすぐに終了するようになっている。コンストラクタ内でCloseすると例外が発生するためやってはいけない。こういう「フォームなんて要らねぇよ」という場合はLoadイベントに匿名メソッドを追加し、その中でCloseするとヨサゲ。

作成するWAVEファイルは、44.1kHz16bit2chの標準的な感じの物。長さは10秒で、ch1/ch2共に1336Hzと941Hzを加算した正弦波を作る。今のところ、自動生成に対応しているのはヘッダのみ。実際の音声データは自前でバイナリレベルを扱う必要がある。でもまぁクラスに配列を渡すと最大4GiB近くを確保しようとしてOutOfMemoryExceptionが発生しちゃうから仕方ないね!!
BinaryWriterにちまちま書き込んでいけば4GB(4*10^9byte)の波形データでも扱える(書きだすのに非常に時間が掛かるが)。大容量のデータをR/Wする場合はすべてをバッファに読み出すことは不可能なので、その辺りをうまくゴニョゴニョする必要がある。

このプログラムで作ったWAVEをSoundEngine Freeで開いてFFTするとこんな感じ。


2015年8月16日日曜日

SQLiteに追加する

すでにSQLiteConnection SQLiteConnにデータベースが開かれているとする。

{
    string[][] Adds =
    {
        new string[] { "422100000", "422175000", "業務用ch5", "NFM", "Unidentified", "", "Unidentified", "0", "0", "Blackman-Harris 4", "8000", "440", "1", "82", "600", "", "2015-08-15T00:00:00", "0", "0" },
    };
            
    using (SQLiteTransaction sqlt = SQLiteConn.BeginTransaction())
    {
        using (SQLiteCommand command = SQLiteConn.CreateCommand())
        {
            foreach (string []Add in Adds)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("insert into Data('Frequency','Center','Description','Mode','Protocol','Call Sign','Service','Shift Enabled','Shift','Filter Type','Filter Bandwidth','Filter Order','Squelch Enabled','Squelch','CW Shift','Notes','Date','Locked','Flagged') values(");

                foreach (string s in Add)
                {
                    sb.Append("'");
                    sb.Append(s);
                    sb.Append("',");
                }

                sb.Remove(sb.Length - 1, 1);
                sb.Append(")");

                //MessageBox.Show(sb.ToString());
                //Clipboard.SetText(sb.ToString());

                command.CommandText = sb.ToString();
                command.ExecuteNonQuery();
            }
        }
        sqlt.Commit();
    }

    MessageBox.Show("ok");
}

レコードを追加するにはSQLiteCommandのCommandTextプロパテのsetを使うらしい。"insert into Data"でDataテーブルに追加できる。要素名はテーブル名の後ろのカッコ内にカンマ区切りで書く。要素名は必ずしもシングルクォーテーションで囲む必要はないが、半角スペースとかが入る場合はシングルクォーテーションで囲む必要がある。とりあえずは囲んでおいたほうが無難。
データについては"values"のあとに書き込む。

今までデータを扱うようなプログラムを書いたことがなかったので、かなり苦労してる。まぁこれをちゃんと動くようにできれば別のものを作るときにはもっと楽にできる と考えて作ってるわけだが。最初っから趣味でプログラムを書いてると、大規模なプログラムになった時に大変だねぇ。。。

2015年8月15日土曜日

SQLiteを読む

SDR#のFrequency Managerは周波数をXMLで保存しているので、これを書き換えるにはExcelで十分なのだが、スキャンをしようとするとFrequency Manager + Scannerが必要で、こいつはXMLではなくData Base Fileで周波数などを保存している。そしてこれはSQLiteというモノらしい。
Frequency Manager + ScannerのUIは非常に使いづらく、周波数を5個入力するだけでも大変。ということでどうにかできないかと模索中。とりあえずSQLiteは読めるようになった。大抵の場合読めれば書くのは楽だが、Data Baseはどうなんだろう?

C#でSQLiteを読むにはここからsqlite-netFx45-setup-bundle-x64-2012-1.0.97.0.exeをダウンロードしてくる。今回はx64を使ったが、x32のほうが使い回しできていいと思う。またC#の参照に追加するにはインストールディレクトリから探してこないとダメみたい。僕の環境ではC:\Program Files\System.Data.SQLite\2012\binにSystem.Data.SQLite.dllが入っているので、こいつを追加した。またVisual StudioはExpress 2015を使ったが、Any CPUでは警告が出るために構成マネージャでx64のプラットフォームを設定した。これは2013とかでも同じ。

以下、SQLiteのデータベースを読むためのコード。

using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Text;
using System.Windows.Forms;

namespace FreqMgr
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void OpenButton_Click(object sender, EventArgs e)
        {
            string Path = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\SDRSFM";
            string[] FileNames = FileSelect(Path, "FreqMgr.db", "Data Base File|*.db|All File|*.*");

            if (FileNames == null)
            {
                return;
            }

            List<string> tList = new List<string>();

            string[] DataNames = {
                "Id", "Frequency", "Center", "Description", "Mode", "Protocol", "Call Sign", "Service",
                "Shift Enabled", "Shift", "Filter Type", "Filter Bandwidth", "Filter Order",
                "Squelch Enabled", "Squelch", "CW Shift", "Notes", "Date", "Locked", "Flagged"
            };

            StringBuilder sb = new StringBuilder();

            using (SQLiteConnection conn = new SQLiteConnection("Data Source=" + FileNames[0]))
            {
                conn.Open();

                using (SQLiteCommand command = conn.CreateCommand())
                {
                    command.CommandText = "SELECT * FROM Data";

                    using (SQLiteDataReader reader = command.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            sb.Clear();
                            foreach (string DataName in DataNames)
                            {
                                sb.Append(reader[DataName].ToString() + ",");
                            }
                            tList.Add(sb.ToString().Substring(0, sb.Length - 1));
                        }
                    }
                }

                conn.Close();
            }

            sb.Clear();

            foreach (string DataName in DataNames)
            {
                sb.Append(DataName + ",");
            }
            sb.Remove(sb.Length - 1, 1);

            for (int i = 0; i < tList.Count; i++)
            {
                sb.Append("\n" + tList[i]);
            }

            //MessageBox.Show(sb.ToString());
            Clipboard.SetText(sb.ToString().Replace(',', '\t'));
        }

        private string[] FileSelect(string InitialDirectory, string FileName, string Filter, bool Multiselect = false)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.InitialDirectory = InitialDirectory;
            ofd.FileName = FileName;
            ofd.Filter = Filter;
            ofd.Multiselect = Multiselect;

            if (ofd.ShowDialog() != DialogResult.OK)
            {
                return (null);
            }

            return (ofd.FileNames);
        }
    }
}

データを表示するとこうなる。

とりあえずFrequency,Center,Description,Mode,Filter Bandwidthがあれば事足りそう。Dateは謎なデータだ。日しか使われていないし、UTCではなくローカルタイムのようだ。Tと書いてあるからといってUTC-07でもないらしい。おそらくDateは追加日で表示をソートするのに使ってる程度だと思うので、あんまり気にしなくても良さそう。

あとはSQLiteにデータを書き込むことができれば何とかなりそうだ。

2015年7月24日金曜日

PictureBoxで右クリック等を読み取る

C#のPictureBoxでクリック状態を検出する場合、Clickイベントでは右クリックを検出することは出来ません。右クリックを読み込むにはMouseDownイベントを使用する必要があります。

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

namespace PictureBoxClick {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();

            pictureBox1.MouseDown += pictureBox1_MouseDown;
            pictureBox1_MouseDown(pictureBox1, new MouseEventArgs(MouseButtons.None, 0, 0, 0, 0));
        }

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e) {
            bool LeftClick = MouseButtons.None != (e.Button & MouseButtons.Left);
            bool RightClick = MouseButtons.None != (e.Button & MouseButtons.Right);
            bool MiddleClick = MouseButtons.None != (e.Button & MouseButtons.Middle);
            bool XButton1Click = MouseButtons.None != (e.Button & MouseButtons.XButton1);
            bool XButton2Click = MouseButtons.None != (e.Button & MouseButtons.XButton2);
            Point Location = e.Location;

            Bitmap bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);

            using (Graphics gra = Graphics.FromImage(bmp))
            using (Font font = new Font("MS ゴシック", 12)) {
                gra.Clear(Color.Black);

                StringBuilder sb = new StringBuilder();

                sb.Append("Left    :" + LeftClick + "\n");
                sb.Append("Right   :" + RightClick + "\n");
                sb.Append("Middle  :" + MiddleClick + "\n");
                sb.Append("XButton1:" + XButton1Click + "\n");
                sb.Append("XButton2:" + XButton2Click + "\n");
                sb.Append("Location:" + Location + "\n");

                gra.DrawString(sb.ToString(), font, Brushes.White, new PointF());
            }

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

Leftは左、Rightは右、Middleはホイール、XButton1は戻るボタン、XButton2は進むボタン が割り当てられています。またこれらはMouseButtons列挙体のORされた値ですし、実際にnew MouseEventArgsで各ボタンをORして渡せば複数のボタンクリック状態を引数に与えることができます。しかし、実際の動作においては複数ボタンをクリックした場合、クリックした順番で複数回のイベントが発生します。これはクリックしたタイミングの問題というより、内部処理の問題のようです。例えば、user32.dllを使用して左クリックと右クリックをORして同時に行わせた場合、2回のイベントが発生します。基本的にMouseDownイベントだけではマウスの同時押しによるファンクションは行えないと考えたほうがいいでしょう。

どうしても同時押しを検出したい場合は以下のようにします。

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

namespace PictureBoxClick {
    public partial class Form1 : Form {
        bool LeftClick, RightClick, MiddleClick, XButton1Click, XButton2Click;

        public Form1() {
            InitializeComponent();

            pictureBox1.MouseDown += pictureBox1_MouseDown;
            pictureBox1.MouseUp += pictureBox1_MouseUp;
            pictureBox1_MouseDown(pictureBox1, new MouseEventArgs(MouseButtons.None, 0, 0, 0, 0));
        }

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e) {
            System.Diagnostics.Debug.WriteLine(e.Button.ToString());

            if (MouseButtons.None != (e.Button & MouseButtons.Left)) {
                LeftClick = true;
            }

            if (MouseButtons.None != (e.Button & MouseButtons.Right)) {
                RightClick = true;
            }

            if (MouseButtons.None != (e.Button & MouseButtons.Middle)) {
                MiddleClick = true;
            }

            if (MouseButtons.None != (e.Button & MouseButtons.XButton1)) {
                XButton1Click = true;
            }

            if (MouseButtons.None != (e.Button & MouseButtons.XButton2)) {
                XButton2Click = true;
            }

            Draw();
        }

        private void pictureBox1_MouseUp(object sender, MouseEventArgs e) {
            System.Diagnostics.Debug.WriteLine(e.Button.ToString());

            if (MouseButtons.None != (e.Button & MouseButtons.Left)) {
                LeftClick = false;
            }

            if (MouseButtons.None != (e.Button & MouseButtons.Right)) {
                RightClick = false;
            }

            if (MouseButtons.None != (e.Button & MouseButtons.Middle)) {
                MiddleClick = false;
            }

            if (MouseButtons.None != (e.Button & MouseButtons.XButton1)) {
                XButton1Click = false;
            }

            if (MouseButtons.None != (e.Button & MouseButtons.XButton2)) {
                XButton2Click = false;
            }

            Draw();
        }

        void Draw() {

            Bitmap bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);

            using (Graphics gra = Graphics.FromImage(bmp))
            using (Font font = new Font("MS ゴシック", 12)) {
                gra.Clear(Color.Black);

                StringBuilder sb = new StringBuilder();

                sb.Append("Left    :" + LeftClick + "\n");
                sb.Append("Right   :" + RightClick + "\n");
                sb.Append("Middle  :" + MiddleClick + "\n");
                sb.Append("XButton1:" + XButton1Click + "\n");
                sb.Append("XButton2:" + XButton2Click + "\n");

                gra.DrawString(sb.ToString(), font, Brushes.White, new PointF());
            }

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

このように、DownでフラグをTrueにし、UpでFalseにすれば、そのフラグを確認することにより任意のタイミングでマウス状態を獲得できます。ただ、この方法でも検出出来ない場合があることに注意が必要です。僕の環境では左・中・右の同時押しは検出できましたが、戻る・進む・右の同時押しは検出できませんでした。
そもそもマウスボタンの同時押しなど通常の動作状態ではありませんから、余程の理由がない限りは使用するべきではないと思います。

2015年7月22日水曜日

モデルロケットを撮りたい場合

今まで何回かモデルロケットを撮っているので、撮影条件をまとめてみようと思います。


シャッター遅めでロケットがブレるような写りの場合。高速感が有ってよろしい。しかしデザインはわからない。

ロケットが十分に加速していない場合(点火直後の場合)は640分の1秒でもデザインがわかる。

読みづらくて申し訳ないです。ランチロッドを抜けてしばらく経ったあたり。かなりの速度の状態だと1000分の1秒でもブレ気味。

開傘直後あたりまでは結構速力がある。ただしこの辺りになるとシャッタースピードよりも、フォーカスが大変。なんせ発射台と最上点では距離が2倍とか3倍とかになるので。


どうしても、絶対にモデルロケットを撮りたい場合、設定はシャッター優先モードで2000分の1秒以上か、マニュアルで設定するといいと思う。上空は地上よりも明るいので、マニュアルの場合は上空を撮った時に白飛びしない程度に設定。明るさは後からPsとかLrとかで修正する。フォーカスはマニュアルでランチャーに焦点を設定。上空で余裕があればマニュアルなりオートなりで再合焦させる(大抵の場合は上手く合焦しない)。とにかく数を撮って偶然を当てにする。撮影モードは連写。高級な(バッファーが多い)カメラほど有利になる。

イベントを記録したいなら、例えば子どもと参加している親御さんなら、カメラはロケット側ではなく、子供を撮影するべき。あの笑顔は大抵の場合ロケット打ち上げの写真を捨てるに値する。ロケットが写ってる写真は、発射前と発射後に手に持ってる写真を撮ればいい。大抵は打ち上げ後のほうがいい写真だと思うけど、万が一木に引っかかったとかで回収できなかった時のために、打ち上げ前にもとっておく。打ち上げ直前だと不安かもしれないので、モデルロケットを作り終わった時にも撮っておくといいかも。ノリがいい子供ならカメラを構えるとモデルロケットを付き出して自慢してくれる。


おまけ


ハイブリッドロケットを撮る場合。とにかく高速なシャッター(数千分の1秒)で、とにかく拡大して撮る。見失う?気にするな。ショックダイヤモンドはカッコいいぞ。


「ロケットを撮りたいです!カメラを買いたいんですけど何がいいですか?」って人はあんまりいないと思うけど、解像度が高いカメラは後からトリミングできるので広角気味のレンズで撮れる。広角レンズだと見失うことが少ないのでよろしい。連写は機能が有ればあんまり違いはないんじゃないかな。むしろバッファが多いほうがいい。20枚連写程度じゃ物足りなくなる。RAW記録できると後から現像するときにWBとか明るさとかいじりやすいけど、数百枚も撮ると見るのも嫌になる。 
個人的にはPENTAXをおすすめしておく(完全に個人の偏見で申し訳ないが)。ペンタのカメラは大抵が防水モデルで、汚れても丸ごと水洗いできるので、ハイブリッドロケットとかを打てるような辺鄙な場所で、転んでカメラを汚したとしても安心して対応できる。

2015年7月20日月曜日

60rdマガジンを買った

SUREFIREの60rdマガジンサイズの電動ガン用マガジンを買いました。

左が通常のマガジンサイズで、右2本が60rdマガジンサイズです。

中央が改造済み、右が未改造です。通常は850発のゼンマイマガジンです。850発だと面白く無いのでノーマルマガジンのメカを突っ込んでいます。ノーマルマグは80発程度ですから、おおよそリアルカウントっぽくなります。またゼンマイマガジンでは動かすとシャカシャカうるさいですが、ノーマルマグであれば静かです。

M4に装着するとこんな感じです。上の写真を見ても分かる通り、60rdマガジンはあまり長さは変わらないので、横からみると普通のM4に見えます。前から見るとフレームくらいの幅があるのでちょっとイカツイかもですが。

ディスカバリーチャンネルでの謎の資料映像でドアエントリーを行う隊員が長いマガジンを使用していました。通常マガジンの2倍か3倍の装弾数になるので、中でどれくらい撃つか分からない場合には有効だと思います。
反面、長さがある分伏せて撃つのは大変です。そのため、遠距離で掩体に寝そべりながら撃つような用途では使いづらいでしょう。



最後に公式動画を1つ。動画の中では100rdのマガジンを使用しています。さすがに100rdになるとかなり長く、確かにキモいなぁ という感じです。

2015年7月16日木曜日

ストロボビーコン(レプ)を買ってみた

"FMA S&S Precisionタイプ マンタ ストロボライト"というレプリカストロボを買ってみました。購入店はモケイパドック通販です。ちなみに通販では4298円ですが、箱には店頭販売用の3980円というシールがついていました。それとおそらく色の確認用ですが、小さな緑のシールも貼ってありました。裏にIR & GREENって書いてあるのにね。


外観はこんな感じ。左上の部分からCR123を入れる。そしてキャップについてるオルタネイトスイッチで全体のON/OFFを行う。左右に付いているスイッチを両方押して長押しすると、離した時から可視光が光る。一旦可視光になったらオルタネイトスイッチでOFFにしてからONにしないとIRに戻らないみたい。
システムONになった時にバイブレータモータで3回振動する。が、振動は大きくないので手に持ってないとわからないと思う。それと、振動よりも駆動音のほうが大きいので、そっちのほうが判断に使いやすい。また、可視光の時はバイブは動かないみたい。

緑では大きな2個のLEDが光る。かなり眩しい。IRの時は3個のLEDが点灯する。IRはもちろん肉眼では見えないが、全く見えないわけではなく、若干可視側に漏れがあるので、日没後のある程度暗くなった時点で目で見えるようになる。

点灯時はこんな感じ。めちゃくちゃ明るい。IRもNVで見れば相当に明るい。パルス点灯だからいいものの、ヘタすると増幅管が焼けるようなレベル。3mほど離れたところから見ても増幅管の発光が眩しい。やはりビーコンは遠くから見るものだ。

内蔵されてるLEDは秋月で10個150円とか200円とかで売ってるような見た目。なんともチープ。

このストロボ、ヘルメットに装着することを前提に作られているので、ベルクロ面は曲面になっている。なので平面、例えばプレートキャリアとかバックパックのベルクロには綺麗に貼れない。くっつかないこともないが、ちょっと不安な感じ。

また落下防止のワイヤを通す穴がないので、確実に固定しておかないと、気がついたら紛失していた ということになりかねない。正確には穴はあるのだけど、細い針金1本通る程度の穴なので、柔軟性があり切れ辛い糸を通しておく必要がある。


一応NVで動画もとってみた。が、あんまり綺麗に見えないね。肉眼だと綺麗に見えるんだけど、光学系がどうしようもない。




これは買いか? と聞かれると悩む。正直、コスプレ以外には使い道がない。というのは、例えばLITEBUCK LEDマーカーであれば点滅以外にも点灯ができるので、普通に明かりとして使えるし、赤色なら目にも優しいので天体観測時に非常に役に立つ(なにせ-20℃でも水没しても使えるのだ。これほど信頼できるアイテムは数多くないだろう)。しかしこのビーコンではIR/可視光の点滅しかできない。そのためにサバゲでNVを使用した相手に対するビーコンとしてのみしか使えないのだ。
というわけで、積極的にオススメできるものではない。

C#のContextMenuStripでどのコントロールをクリックしたのかを知る

C#で右クリックするにはContextMenuStripを使用します。基本的にはToolStripMenuItemのクリックイベントを使用します。しかしそれだけでは「ContextMenuStripに設定されたどれか」しかわかりません。ということでどうにかします。が、結構簡単なのでソースコードを見たほうが楽でしょう。

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace ContextMenuStripTest {
    public partial class Form1 : Form {
        Control ContextMenuStrip1_Source;

        public Form1() {
            InitializeComponent();

            contextMenuStrip1.Opening += contextMenuStrip1_Opening;
            copyToolStripMenuItem.Click += CopyToolStripMenuItem_Click;
        }

        private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) {
            ContextMenuStrip Menu = sender as ContextMenuStrip;

            if (Menu == null) {
                return;
            }

            ContextMenuStrip1_Source = Menu.SourceControl;
        }

        private void CopyToolStripMenuItem_Click(object sender, EventArgs e) {
            if (ContextMenuStrip1_Source == null) {
                return;
            }

            Clipboard.SetText(ContextMenuStrip1_Source.Text);
        }
    }
}

OpeningイベントでContextMenuStrip.SourceControlを獲得し、広域変数に設定します。このSourceはControl型です。そのためControlから継承された、例えばTextなどはそのまま使用することができるため、いくつかのテキストボックスに1つのContextMenuStripを設定し、そのテキストボックスに入っている文字列を獲得する ということが出来ます。

2015年7月11日土曜日

線分と線分の当たり判定

線分同士の当たり判定が欲しくなったので試してみた
C#のFormで、FormにはPictureBoxを親コンテナにドッキングして配置する。またPictureBoxのClickイベントを使用する。
青い斜めの線は固定となり、もう一方の線はマウスで二箇所をクリックすることにより、それらを接続する線を引くことができる。2本の線が交差している場合は赤で、交差していない場合は緑で表示する。
アルゴリズムは 線分交差判定 - juntkの日記 に書いてある物をそのまま使用させてもらった。

線分同士の当たり判定は、例えば現在位置と未来位置で線を作り、進入禁止エリアを囲うラインと当たり判定を行い、衝突が発生した場合は移動させない というような使い方ができると思う。ただし線分同士の衝突範囲では、万が一線で囲ったエリアの中に入り込んでしまった場合には、外にでることができなくなるという問題がある。また、今いる場所が進入禁止エリアの中なのか、それとも稼働可能範囲なのかを判定することは出来ない。

それと、線分同士の当たり判定では始点と終点を使用するため、線のどの部分で衝突しているかを判定することは出来ない。もしもどの場所で衝突しているかを調べる必要があるなら、二分探索等で十分に線が短くなるまで探していくか、他の方法を考える必要がある。


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

namespace LineCollisionDetection {
    public partial class Form1 : Form {
        LinePoint Line1, Line2;
        Point? Start;
        Pen Pen1, Pen2, Pen3;

        public Form1() {
            InitializeComponent();

            Pen1 = new Pen(Color.Red, 3);
            Pen2 = new Pen(Color.Green, 3);
            Pen3 = new Pen(Color.Blue, 3);

            Line1 = new LinePoint(new Point(100, 100), new Point(300, 300));
            Line2 = new LinePoint(new Point(300, 100), new Point(100, 300));

            Size += new Size(400, 400) - pictureBox1.Size;
            MaximumSize = MinimumSize = Size;

            Draw();
        }

        ~Form1() {
            Pen1.Dispose();
            Pen2.Dispose();
            Pen3.Dispose();

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

        private void pictureBox1_Click(object sender, EventArgs e) {
            Point mouse = pictureBox1.PointToClient(Cursor.Position);

            if (Start == null) {
                Start = mouse;
            } else {
                Line1 = new LinePoint(Start ?? new Point(), mouse);
                Start = null;

                Draw();
            }
        }

        public void Draw() {
            Bitmap bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);

            using (Graphics gra = Graphics.FromImage(bmp)) {
                bool Collision = LinePoint.IsCollision(Line1, Line2);

                gra.DrawLine(Pen3, Line2.S, Line2.E);
                gra.DrawLine(Collision ? Pen1 : Pen2, Line1.S, Line1.E);
            }

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

        class LinePoint {
            public Point S;
            public Point E;

            public LinePoint() {
                S = new Point();
                E = new Point();
            }

            public LinePoint(Point S, Point E) {
                this.S = S;
                this.E = E;
            }

            public LinePoint(LinePoint Line) {
                S = Line.S;
                E = Line.E;
            }

            static public bool IsCollision(LinePoint Line1, LinePoint Line2) {
                return ((
                    ((Line2.S.X - Line2.E.X) * (Line1.S.Y - Line2.S.Y) + (Line2.S.Y - Line2.E.Y) * (Line2.S.X - Line1.S.X)) *
                    ((Line2.S.X - Line2.E.X) * (Line1.E.Y - Line2.S.Y) + (Line2.S.Y - Line2.E.Y) * (Line2.S.X - Line1.E.X)) <= 0) && (
                    ((Line1.S.X - Line1.E.X) * (Line2.S.Y - Line1.S.Y) + (Line1.S.Y - Line1.E.Y) * (Line1.S.X - Line2.S.X)) *
                    ((Line1.S.X - Line1.E.X) * (Line2.E.Y - Line1.S.Y) + (Line1.S.Y - Line1.E.Y) * (Line1.S.X - Line2.E.X)) <= 0));
            }
        }
    }
}

2015年7月10日金曜日

ゲームパッドの値をシリアルポートに吐き出す

ゲームパッドを使いたい と思うことは度々あると思います。が、面倒ですよね? Javaとかいう言語を使ったりするのは。ということでC#でやってみたいと思います。
ここでは条件として、Xbox360のコントローラに限定したいと思うます。そうすることでC#で簡単に使えるようになります。

Xbox360のコントローラなんてダサいって? このコントローラは米軍も使用している由緒正しいコントローラです!

さて、今回はXNA Game Studioというフレームワークを使用しました。これは既に開発が終了していますが、C#でゲームを動かすために作られたもので、2Dや3Dなどのレンダリングをすることが出来ます(もっとも、レンダリングをすること"しか"できないので、3Dの表現は大変ですが)。
また、XNAはXboxのゲーム開発環境としても想定されており、年間所定のライセンスを支払うとXboxで遊んだり、安価に販売することも出来たようです。そのために、XNAにはXbox360のコントローラを使用するためのGamePadStateというクラスが用意されています。

この機能を使用するためにはまず参照設定でMicrosoft.Xna.Frameworkを読み込む必要があります。また表示のためにPictureBoxをフォームに貼り付け、AnchorをTop, Bottom, Left, Rightに設定して下さい。そしてFormにはLoadとFormClosedのイベントを作成し、またTimerとSerialPortのコンポーネントを追加して下さい。
今回のGUIではシリアルポートのOpen・Close処理は起動時・終了時に行います。そのためSerialPortコンポーネントで使用するCOMポート名と通信フォーマットを適切に設定しておいて下さい。
それから、Timerはゲームパッドの読み込みに使用します。Enableはtrueにし、Intervalは適当な値(50-500程度)に設定して下さい。

実際にデータを読んで送信するための処理はソースコードを読んでみてください。画像にして表示する部分が半分以上で、実際の送受信処理は非常に簡単です。

今回は左スティックのみを使用しましたが、XNAではXboxボタン(中央にある椎茸みたいなボタン)以外のすべてのボタンを読み込むことが出来ます。そのため、XBee等を使用して戦車のラジコンを操縦しようと思えば、FPSゲームと全く同じコントロール方法で制御することも可能になります(もちろん操作対象にマイコンを組み込んで操作を理解させる必要がありますが)。また、情報を読み込む以外にも、コントローラの振動を制御することも可能です。これは例えば戦車のラジコンであれば、ラジコン側に加速度センサを搭載しておき、一定以上の振動が検出されたらゲームパッドを振動させる事により、段差を乗り越えたらコントローラを通じて操縦者に伝えることが出来ます。他にも、主砲を撃つ操作をした時に振動させるなどの演出もできるでしょう。
また、XNAではGamePad.GetStateの引数にPlayerIndex.Oneを渡していますが、これは1-4までを指定できます。つまり1台のPCで最大4台のコントローラを区別することが出来ます。これは4台の戦車で対戦したりとか、1個の複雑なロボットアームを複数人で操作することが可能になります。


using System;
using System.Drawing;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace XnaPortDump {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();

            Size += new Size(400, 400) - pictureBox1.Size;
        }

        private void timer1_Tick(object sender, EventArgs e) {
            GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);

            float x = gamePadState.ThumbSticks.Left.X;
            float y = gamePadState.ThumbSticks.Left.Y;

            Bitmap bmp = new Bitmap(pictureBox1.Width, pictureBox1.Height);
            using (Graphics gra = Graphics.FromImage(bmp)) {
                gra.FillEllipse(Brushes.Lime, bmp.Width / 2 + (x * bmp.Width / 2 * 0.95f) - 5, bmp.Height / 2 - (y * bmp.Height / 2 * 0.95f) - 5, 10, 10);
            }

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

            x *= 2500;
            y *= 2500;

            serialPort1.Write("motorrun 0 " + x.ToString("0") + "\n");
            serialPort1.Write("motorrun 1 " + y.ToString("0") + "\n");

            Text = x.ToString("+0000;-0000") + " " + y.ToString("+0000;-0000");
        }

        private void Form1_Load(object sender, EventArgs e) {
            serialPort1.Open();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
            serialPort1.Close();
        }
    }
}

2015年7月9日木曜日

イリジウムフレアを撮ってみた



イリジウム7のフレアを撮ってみました。流れ星じゃないです。
上はK-5、下はα7sで撮影しています。α7sは動画で撮影した後に静止画として書き出し、比較明合成を行いました。
K-5は換算27mmで撮影してトリミングです。α7sは55mで撮影してトリミングせず。若干狙いが外れてました。

イリジウムフレアは確かに輝度が変化して面白いですが、それでも「あ~ なんか光ってるな~」程度です 見てて楽しいか・面白いかどうかは人それぞれですね。。