2015年7月3日金曜日

C#でシリアルポートのデバイス名を獲得する

C#のSystem.IO.Ports.GetPortNames()ではCOMxというそっけない文字列しか獲得できない
あんまり不便してないのだけど、試しにデバイス名を獲得してみた

C#でCOMポート番号とシリアル接続機器名を同時に取得する方法 - 真実の楽譜(フルスコア)というページを参考にした 手っ取り早く使いたいならこのページでどうぞ

さて、とりあえず試してみたソースコード

/*
 * 参考にしたページ : http://truthfullscore.hatenablog.com/entry/2014/01/10/180608
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Management;

namespace ShowSerialPortName {
    class Program {
        public static void Main() {

            Stopwatch sw = new Stopwatch();
            sw.Start();
            string[] ports = GetDeviceNames();
            sw.Stop();

            Array.Sort(ports, (a, b) => (DeviceNameToIndex(a) - DeviceNameToIndex(b)));

            for (int i = 0; i < ports.Length; i++) {
                Console.WriteLine(ports[i]);
            }

            Console.WriteLine((sw.ElapsedMilliseconds * 0.001).ToString() + "sec");
            Console.ReadLine();
        }

        public static int DeviceNameToIndex(string DeviceName) {
            System.Text.RegularExpressions.Regex check = new System.Text.RegularExpressions.Regex("(COM[1-9][0-9]?[0-9]?)");

            int i = DeviceName.LastIndexOf('(');

            if (i == -1) {
                return (0);
            }

            string str = DeviceName.Substring(i);

            if (!check.IsMatch(str)) {
                return (0);
            }

            if (str.Length <= 5) {
                return (0);
            }

            int index;
            if (!int.TryParse(str.Substring(4, str.Length - 5), out index)) {
                return (0);
            }

            return (index);
        }

        public static string[] GetDeviceNames() {
            List<string> deviceNameList = new List<string>();
            System.Text.RegularExpressions.Regex check = new System.Text.RegularExpressions.Regex("(COM[1-9][0-9]?[0-9]?)");

            ManagementClass mcPnPEntity = new ManagementClass("Win32_PnPEntity");
            ManagementObjectCollection manageObjCol = mcPnPEntity.GetInstances();

            foreach (ManagementObject manageObj in manageObjCol) {
                string name = manageObj.GetPropertyValue("Name") as string;

                if (check.IsMatch(name)) {
                    deviceNameList.Add(name);
                }
            }

            return (deviceNameList.ToArray());
        }
    }
}

僕の環境ではこうなった
たった5個のシリアルポート表を入手するだけで約0.3秒もかかる。これは200以上もあるデバイスすべてに総当りで探しているので仕方ないといえば仕方ないが。PCによってはもっと時間がかかる場合もあるだろうし、きれいなPCならもっと早いかもしれない。が、どちらにしろGetPortNamesよりははるかに長いだろう。GUIフォームで使用する場合はGUIスレッドをブロックしないような方法を考える必要がありそうだ。

それとデバイス名の最後にインデックスが表示されるのはあんまり好かない。LastIndexOfで(COMx)を切り出して前に移動する という処理をしたほうが読みやすいと思う。ただこれは僕がCOMポートのテーブルを把握しているからなので、どのポート番号がどのデバイスに接続されているかわからない場合はCOMx表示がどこにあっても関係ないかも?。

2015年6月21日日曜日

STM32F1でDMAを使ってGPIOをトグルする

今回はTIM3の更新イベントでGPIOEに16bitデータを出力する

まずはクロックの供給 GPIOE、TIM3、DMAを有効にする
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

次にペリフェラルを初期化する
TIM_TimeBaseInitTypeDef TIM_InitStructure;

TIM_InitStructure.TIM_Prescaler = 0;
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStructure.TIM_Period = 11;
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;

TIM_TimeBaseInit(TIM3, &TIM_InitStructure);


GPIO_InitTypeDef GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOE, &GPIO_InitStructure);


DMA_InitTypeDef DMA_InitStructure;

DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&GPIOE->ODR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(&LineBuffer[0]);
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 200;
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_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

DMA_DeInit(DMA1_Channel3);
DMA_Init(DMA1_Channel3, &DMA_InitStructure);

DMA_Cmd(DMA1_Channel3, ENABLE);
TIM_DMACmd(TIM3, TIM_DMA_Update, ENABLE);

LineBufferはuint16_t広域変数の配列になる。BufferSizeは直接書いてるが、本来はsizeofを使ったほうがよろしい。後は TIM_Cmd(TIM3, ENABLE) を呼べばタイマが動作してDMAも動作する。

注意点として、あまり早い動作は出来ない。最大で6MHz程度まで(72MHzのSTM32F103VEの場合)。16bitバスの6MHzだと96Mbit/secだが、液晶だとクロックレートMin7MHzとか要求してくるのでちょっと足りない。ラッチ可能な入力の場合はFSMCとかを使ったほうがいいかも。

2015年6月7日日曜日

α7sについて

2015/06/22 追記:フリッカーの比較
***

「α7sってどうなの?」って聞かれても上手く説明できないので、とりあえず文字にしてみようと思います

・いいところ
1) 常用感度が高い
α7sの唯一のメリットですね
ISO5万くらいは使えます 「ノイズ出るの当たり前でしょ」みたいな用途なら10万とか20万くらいにはできます 最大の40万にすると四方に紫のノイズが出ますが、個体差の問題なのかも ランダムノイズではなくパターンノイズっぽいので後から補正できると思いますが(未確認)
この感度では手持ちで星やISSの動画を撮れます
感度のイメージとしては、F1.8のレンズで動画を撮った場合、瞳孔が開いた人間と同じ程度の感度だと思います

・悪いところ
1) シャッターボタンがクソい
本当にこれは酷いですね 半押ししたつもりがシャッター落ちてたり、そういう感じです
どこが半押しなのかわからない 有る人曰く「最近はこんなもんだよ」とのことですが、これは嫌だなぁ シャッターボタンの交換プログラムとかやるべき

 2) 解像度があんまり高くない
これは感度を稼ぐためにしょうがないですね トリミングとか考えないなら問題無いと思いますが、僕の場合は主にイベントの記録を目的として使っていて、「ぼやっと撮って欲しいところを切り取る」という用途だとつらいです

3) 電池が少ない
画面の隅に残量がパーセント表示されているので、電池残量の減り方が目に見えます ちょっと撮ると10%とか減ってるので結構怖いです 0%まで使う前に予備電池に交換しちゃうので、実際はどこまで保つのかわからないですが、撮ってる時にふと目に入った残量がものすごい減ってると怖いですね

4) ファインダーの切り替えが敏感
ちょっとカメラを体に近づけるとすぐにファインダーに切り替わってしまいます
バリアングルで腹あたりに構えるとファインダーになってしまい液晶が消えてしまうので、使いづらい

5) 起動が遅い
電源を入れてから1枚目を撮るまでにかなりタイムラグが有ります 電池を持たせるために普段は電源を切っておいて、撮りたいときに電源を入れる という使い方はほぼ不可能です

6) フリッカーがスゴい
サイレントモードで撮影すると照明によってはシャッター40分の1秒とかじゃないとフリッカーがスゴいです
サイレントモードを解除すればフリッカーはあんまり気にならないですが、ライブビュー専用カメラなので、シャッターを閉じる→シャッターを開ける→シャッターを閉じる→シャッターを開ける という、ミラー一眼の倍の騒音が出ます かなりウルサイです

・総評
欠点しか出てきてないですが。。。
上にも書いたように、イベントの記録用途に使うべきではありません
望遠マクロを使って喫茶店や料理屋で写真取るとか、街の中を歩きながら静止物を撮るとか、時間に余裕のある用途で使うべきです


最後に、α7sで撮ったサバゲの夜戦動画を置いておきます


***
2015/06/22 フリッカーの比較

上はサイレントシャッターをONにして撮影
下はサイレントシャッターをOFFにして撮影
光源は50Hzの蛍光灯 双方とも1/500sec

自然光で撮影する場合はサイレントシャッターONでもいいと思いますが、人工光で撮影する場合はサイレントシャッターOFFでメカニカルシャッターを使用して撮影したほうが良いと思います

2015年5月29日金曜日

特小とデジ簡の周波数

SDR#のfrequencys.xmlにコピペできるデータ

特小では直接交信が20ch デジ簡では30chあります
広域レシーバや高級機になるとスキャンが出来ますが、あんまり便利じゃないですよね?
ですがSDR#などではスペクトラムを表示できるので、どのチャンネルで送信されているかを目で見て確認できます

デジ簡はSDR#ではデコード出来ないっぽいのでチャンネルの確認にしか使えませんが、特小であれば通常のNFMとして受信することが出来ます

ところでデジ簡ってどんなデータなんでしょう?
DPR-6には4FSKと書いてあるので、デジタル値への変換はできそうで、データフォーマットさえわかればデコードは簡単に出来そうな気がするんですけどねぇ SDRで聞けるようにならないかな


2015年5月18日月曜日

C#でNTPを使ってみる

C#のUDPでNTPにアクセスしてみました
今回使ったサーバーは ntp.nict.jp です(つまりStratum2以上のサーバーでの動作確認はしていません)

実行するとこんな感じになります

nictサーバーでStratumは1 クロックソース識別子は"NICT"です

5個の時間は上から
・サーバーがクロックソースと最後に同期した時間(サーバー時間)
・クライアントがリクエストを送信した時間(クライアント時間)
・サーバーがリクエストを受信した時間(サーバー時間)
・サーバーがレスポンスを送信した時間(サーバー時間)
・クライアントがレスポンスを受信した時間(クライアント時間)
となります
NTPでは2種類の時間系が存在することに注意してください

最初の1つを除いた4個の時間がそれぞれ ts, Tr, Ts, tr となり、サーバーから見たクライアントの遅延は ((Ts + Tr) / 2) + ((ts + tr) / 2) となります
クライアントの時間が進んでいる場合は負の値になります

NTP時間は整数32bit 小数点以下32bitの固定小数点形式です
C#で時間を計算する場合は、Math.Pow(2, -32)をNTPtime(UInt64)に掛けた積が秒となり、1900年1月1日0時0分0秒UTC±0からの経過時間です

C#ではシステム時間を変更することができるので、クライアント時間の遅延量がわかれば適切に修正できますが、気軽にシステム時間を変更するべきではありません
一気に時間を飛ばすと不都合が発生する可能性があるからです(もっとも、Windowsの標準機能でも一気に時間を飛ばしていますが。。。)

***

NTPと通信する場合は、リクエストを送信する直前に送信時間を記録し、レスポンスを受信した直後に受信時間を記録します
送信時間と受信時間の処理はNTPの精度に大きく影響しますが、その前後は精度にはさほど影響しません(もちろんレスポンスを受信してから何日もほっといてしまえば精度は劣化しますが、常識的な処理速度であれば問題ないでしょう)

今回のプログラムは送信前に時間を記録しておき、レスポンスが一定時間なければタイムアウトの例外を投げます
タイムアウトは初期値で2秒で、その間はブロッキングです
一応200ミリ秒程度でレスポンスが帰ってきますが、回線状態によってはもう少し必要な場合もあります

NTPのエンディアンはビッグエンディアンですから、C#のBitConverterのような簡単な処理方法は使えません そのためいちいちビットシフト等で計算しています

***

なぜシステム時間を変更するわけでもないのにNTPの時間を使うかというと、クライアントの遅延時間がわかれば、C#のAddSecondsなどで簡単に補正することが可能だからです

例えば人工衛星の位置を計算したい場合、相手は秒速8km前後で移動しているわけで、システム時間が30秒もズレていたら相手はおよそ250kmも移動してしまいます ISSなどは高度400km程度ですから、250kmというのは無視できません(35度くらい移動してしまいます)

ただNTPは常に使えるわけではなく、最低限何らかのインターネット接続できる環境が必要なので、できればPCの時間は常に構成しておくほうが望ましいです(Windowsって定期的にNTPと同期するはずなのにかなりズレてますよね。。。)

WindowsでNTPと同期した直後に以下のプログラムを走らせても数十ミリ秒位のズレがあるので、あんまり高精度は期待しない方がいいです

それと、最近のNTPでは「64bitNTPtimeの最上位bitが0の場合は2036年以降として処理する」みたいな約束がありますが、以下のプログラムでは未対応なので、あと20年くらいで使えなくなります
ま、20年後にこのコードを使う人もいないと思いますが、注意してください

2015年5月6日水曜日

ftoa関数(ひたすらループする)

浮動小数点数を指定した精度で文字列化する関数です

ftoaが本体です
第1引数:変換する値
第2引数:結果を入れる文字配列
第3引数:指定した長さに足りない場合に0を入れるか(偽で0x20埋め 真で0x30埋め)
第4引数:変換結果の最短長さ
第5引数:小数点以下の長さ

またftoafは第1引数にフォーマットの文字列を取り、第2引数に変換する値を取ります
このフォーマットはprintfの%fに使用するフォーマットに近い書式を設定できます

現在のところNaNやInf等の処理は行っていません
またいくつかのフォーマットを指定してsprintfと同じ結果になるように作っていますが、正しく実装されていない部分や、そもそも実装依存で環境によって違うなどの場合があるかもしれません
ループを多用しているので、コンパイラの最適化に依存しているコードでもあります
また内部で浮動小数点を割ったり掛けたりしているので、その間の演算誤差も無視できない場合があります  とりあえず「sprintfとか使えない環境で臨時に使う」程度で


追記:2015-05-06 15-07
一番最後にp--からp++までの間に入っている四捨五入処理ですが、最後の桁が9で切り上げるときに数字がコロンになってしまう問題があります
一番手っ取り早い対策はこのコードをコメントアウトすることです(もちろん四捨五入はされず、小数点以下切り捨てとされます)
数字が9だった場合は上の桁をインクリメントすればいいのですが、例えば999.96を小数点以下1桁まで表示した場合、繰り上げをすると先頭に1桁増やす必要があります
ということで小さな修正では対応できないので、近いうちにちゃんと作りなおそうと思います
追記ここまで

2015年4月16日木曜日

コリニアアンテナを設置した


コリニアアンテナをベランダに設置してみました
ベランダは南西向きで、北側は上半分程度しか屋根の上に出ていません
塩ビ管の長いパイプの中にコリニアアレイが入っています
右にあるのは144MHzのアンテナです
小さいですがFMラジオはもちろんキューブサットのビーコン(数百km離れた数十mWのCW)でも受信できます




受信結果はこのような感じです
北はサハリンの南端から、南は千歳のあたりまでを受信できています
千歳方向は、かなり高度の高い国際線は取れますが、新千歳に離着陸する機体は取れません

北側のほうがレンジが広いのは、コリニアアンテナの製造誤差によるものだと推定されます
自作する直列コリニアアンテナは6段前後が一番作りやすいと思います


田舎だとアマチュア無線は違法局しかいないし、アンテナを作って遊べるのはADS-Bの受信くらいですが、他国上空の機体も見えるのは結構楽しいです
もっとも、自分の耳ではデコードできないので画面を見てニヤニヤする程度ですか