[ カテゴリー » PC関連 ]

PCでmsec時間測定(6) - 基板完成

blog20100817-MilisecTimerAndSensor

前回Link からほぼ1ヶ月経ちましたが、最初の2週間は依頼主からの仕様決定待ち、その後は部品購入&組み立てで結構忙しかったのです。出てきた仕様が予想とちょっと異なったため、定電流回路Link は不要、代わりにレギュレータを装備することとなりました。見切り発車で部品買わなくてよかった。

写真は一通りに動作確認を終えた回路基板など。ケースの値段も馬鹿にならない&穴あけめんどくせ~と依頼主に言ったら、今回は試作なので100円ショップで売ってるような材料で良いと。というわけでタッパーに入れましたが、あまりかっこ良くないな。3個100円のタッパーは全体的に薄い作りでカッターナイフで穴を開けるには簡単な反面、割れやすかったりドリルで穴あけると材質がドリルに絡み付いてきてきれいな穴が開かない。やっぱり電子工作用のケースはそれなりに利点があるってこと。

右下で赤く光っているのは、本システムの最重要パーツであられます光センサ。数秒単位でOn/Offを繰り返すスイッチLink というのはこれのことで、このセンサの前を通り過ぎる対象を測定するわけです。

なぜ最重要かというと、センサ以外の全回路&部品(写真のAT90USB162のボード含む)を全部合わせてもこのセンサの値段の半分にも満たないためw メーカーは全然値引きに応じないとうちの会社の購買部門も愚痴をこぼす某社。依頼主によると今回も当然ほぼ定価で購入、たくさん買ったらいくらになるかと聞いてもほぼ定価と、今も変わり無いようで何よりw

[20109/25 追記] 続きLink

— posted by mu at 12:37 am   commentComment [0]  pingTrackBack [0]

.NETで順番を守って排他制御

マルチスレッド環境で排他制御する時に、先に要求したスレッドほど先に実行させたい、ということを考える機会がありました。

.NETプログラミングで有名な排他制御方法はMonitorLinkManualResetEventLink でしょうか。C#ではMonitorクラスを便利に使えるlockステートメントもあり、私もよく使います。しかしManualResetEventは単一もしくは複数のスレッドを待たせて一気にスタートさせるものですし、Monitorは待ち状態にある複数のスレッドに対してロックを順番に与えてはくれません。後者はどういうことかというと、
  • スレッド1は既に排他ロックを取得し実行中
  • スレッド2がロックを求めて待ち状態に入った
  • スレッド2より後にスレッド3がロックを求めて待ち状態に入った
という状態でスレッド1がロックを開放した場合、次に排他ロックを取得するのは…どちらか分かりません。待ちスレッドが1個や2個では発生しにくいかもしれませんが、10や20スレッド待たせると順番通りにロックさせてくれないのが分かると思います。

最初に目をつけたのがQueueLink ジェネリッククラス。

using System.Collections.Generic;
using System.Threading;

class FIFOLock
{
  Queue<ManualResetEvent> eventQueue = new Queue<ManualResetEvent>();

  public void GetLock()
  {
    ManualResetEvent myEvent = new ManualResetEvent(false);
    lock(eventQueue)
    {
      eventQueue.Enqueue(myEvent);
      if(myEvent.Equals(eventQueue.Peek())) return;  // No thread is locking resource
    }
    myEvent.WaitOne();
  }


  public void ReleaseLock()
  {
    lock{eventQueue)
    {
      eventQueue.Dequeue();  // Remove ManualResetEvent of this thread
      eventQueue.Peek().Set();  // Signal to next waiting thread
    }
  }
}

実際にVisualStudioに打ち込んでないので、文法間違いなどあるかもしれません。打ち込まなかったのは考えている途中で「あ、lock使ってるからダメだ、これは」と思ったから。複数のスレッドがGetLock()に飛び込んできて競合待ち状態になったときに、次にeventQueueにアクセスできるスレッドが不定なため、順番の追い越しが発生する可能性があります。eventQueueがロックされている時間は僅かで、複数のスレッドがGetLock内で競合する確率は低いとはいえ、完璧ではない。ちなみに.NET 4.0ではConcurrentQueueLink なんていうスレッドセーフなQueueが登場したので、使えるかもしれません。

次にInterlockedLink クラスを使うこと。Incrementメソッドを使えば確実に早くやってきたスレッドから小さい番号を割り当てることができます。しかし単に数字が分かっただけ。各スレッドに自分の順番が来るまで待たせる上手い方法が分かりません。唯一思いついたのが、もう一個Interlockedで管理される整数を用意して、処理が終わったスレッドはこの数を増やしていくこと。銀行とかにある順番待ち整理券がイメージしやすいでしょうか。前者の変数が整理券の番号で、後者が「○番のカードをお持ちの方…」としゃべる機械。自分の番号が来たスレッドは処理を開始。

ただこの方法は各スレッドが自分の順番が来たかどうかポーリングでチェックせねばならず、CPU負荷が高くなる欠点があります。負荷の低いManualResetEventなどを使いたいのですが、Interlocked.Incrementで得られた数字とManualResetEventを結び付けようとする(Dictionary<int, ManualResetEvent>とか)所でスレッドセーフにする方法が思いつかず。

現時点の答えはReaderWriterLockLink を使い、全スレッドAcquireWriterLockを呼び出すこと。とりあえず100スレッドAcquireWriterLockを呼び出して待ち状態に入るプログラムを書いてみましたが、順番通りにスレッドがロックを取得していました。こういう使い方って、保障されているのでしょうか? ちなみにReaderLockされている状態ではAcquireWriterLockは待ち状態にされますが、その後にAcquireReaderLockのスレッドが来ると待ち状態のAcquireWriterLockを平気で追い抜きます(複数同時ReaderLockを認めているから)。

[2011/8/2 追記] 最終文訂正(AcquireReaderLock→AcquireWriterLock)

— posted by mu at 09:44 pm   commentComment [0]  pingTrackBack [0]

30VでLED

blog20100721-ConstantCurrentCircuit

先日の記事Link で触れた回路設計の見直しに関連するのですが、詳しい背景は省略して要求事項を要約すると、
  1. 外部から来る電圧のOn/Offに合わせて普通のLEDを点滅させる。
  2. その電圧は10~30V。外部で使用する電源電圧なので電源を変えない限りは一定。
  3. この電圧以外にLEDが使える電力源はない。
まずこの電圧を普通のLEDに直接入れると確実に焼けてしまいます(5Vでも煙出せますよ)ので、電圧を落とさないといけない。一番簡単なのは直列に抵抗を入れることですが、電源電圧の変動幅が大きいので全電圧に対応できる抵抗値が決まらない。電源がOn/Offを繰り返すのでレギュレータやDC-DC変換回路もダメ。

ここまで電圧電圧と書きましたが、LED制御に重要なのは電流量で電流を決めればLEDにかける電圧が決まるというのが正しい考え方。というわけで、LEDに20mAを流す定電流回路を考えればいいことになります。もっとも簡単なのは定電流ダイオードLink という素子を使うことですが、その場合問題になるのが発熱。

(動力・音・光などを発生しない)回路に電圧をかけて電流を流すと必ずジュール熱Link が発生します。今回の場合なら30V×20mA=0.6W。どこかで必ず発生します。LEDに直接繋ぐと焦げてしまうのはこの熱がすべてLEDで発生するため(実際にはもっと大量の電流が流れもっと発熱します)。抵抗器や定電流ダイオードを直列に繋ぐと、発熱がLEDとで分担されるのでLEDが焼けずにすむわけです。

で、一般的な定電流ダイオードが許容できる発熱量は0.3Wが多いので、完全にオーバー。写真上側のように複数を並列に繋いで発熱を分担させないといけません。こんな米粒みたいな素子がよく0.3Wも耐えられるなと思ったら、リード線を伝わって基板などに放熱させることを前提としているようですね。写真の縦に取り付けてある銅版は放熱目的だったりします。

blog20100721-ConstantCurrentCircuit

別の解決としてはトランジスタを使った定電流回路Link 。発熱はトランジスタ・エミッタに接続された抵抗器・LEDで分担されます。一番発熱するのはトランジスタなのですが、トランジスタは種類が豊富、安くて発熱に強い(コレクタ損失が大きい)やつが一杯あるのが魅力。

しかし…定電流ダイオード3個と比べると、トランジスタを使った回路はユニバーサル基板上では倍の面積を取るんですよね。今回基板面積にもあまり余裕がないので、素直に定電流ダイオードでしょうか(だけど値段高い)。

[参考] 個別半導体の豆知識 (なひたふ電子情報)Link

— posted by mu at 10:39 pm   commentComment [0]  pingTrackBack [0]

PCでmsec時間測定(5) - 校正

AVR側はとりあえずできLink 、Windows側も結構出来上がってきました。センサとの接続部で回路設計の見直しが生じ、プログラムの方は待ち状態になりつつあるため、以前から気になっていた以下のことを検証することに。
正確な時間を測定しているのか
AVRを駆動するクロックをカウントして時間を測定しているので、正確さはクロックのそれに直接依存してます。ストロベリーリナックスLink のボードは16MHzクォーツですのでそれほど狂ってはいないと思いますが。
頻繁にボタン・センサをOn/Offしても正確に測定できるか
回路はOn/Offが切り替わったときだけPC側にその情報を送ります。On/Offが頻繁に起きるとUSB通信も増えるわけで、そういったCPU負荷が高い状態でも時間を正確に刻めるか。
というわけでPC側のプログラムを拡張。
  • PCの時計は信用できないので、あらかじめNTPLink でサーバに同期。
  • AVR側は10kHz(1秒間に1万回)、内部のカウンタを延々と+1し続けます。PCから命令を受けるとその時の内部カウンタをPC側に返す機能を追加。
  • PC上のWindowsプログラムは1時間ごとにAVR側に内部カウンタを報告させるコマンドを発行、1時間の間にカウンタがいくつ増加しているかを調べる。
  • 上の測定をスイッチのOn/Off無しと頻繁に繰り返す状態で行い、比較する。
On/Offの繰り返しは手元にあったPIC 12F683でM系列Link を発生させるプログラムを作成、AVR側のスイッチ入力としました。AVRのクロックが正確なら、1時間後のカウンタは3600万増えているはず。測定の結果は以下の通り。
スイッチOn/Off 1時間のカウント増加 1秒あたり
なし 35998815.95 9999.67
あり 35998770.97 9999.66

今やっているのはミリ秒単位の測定なので、1秒間1万カウント中影響が出てくるのは10の位。しかしAVR側のカウンタの狂いはその2桁下、スイッチOn/Off有無による違いは更に1桁下。

スイッチOn/Off間隔が数分にも及ぶような測定の場合はミリ秒単位に影響が出てくるのでこの結果による補正が必要になってきますが、今前提としているのは1~2秒間隔。無視しても良さそうですねぇ。

[2010/9/25 追記] 続きLink

— posted by mu at 07:11 pm   commentComment [0]  pingTrackBack [0]

シリアルポートの名前取得

blog20100704-GetSerialPortEntries

ミリ秒測定のAVR側は一段落ついたLink ので、Windows側のプログラムに着手。Windows側からはシリアルポートと認識されるので、そのポートと通信するプログラムにすればOK。実際これまでの動作確認はTeratermLink を使ってました。.NET Framework 2.0以降はシリアルポートをサポートLink してますので、Visual C# 2010 Expressなら通信もさほど苦労しないでしょう。

まずは利用可能なシリアルポートの取得。最初に見つけたのはSystem.IO.Ports.SerialPort.GetPortNames()。これは'COM1', 'COM2'といったポートの番号を返してくれます。しかしTeratermでは'通信ポート (COM1)', 'AT90USBxxx CDC USB to UART MGM (COM3)'のようにより詳細な名前が表示され分かりやすいので、できればこういう表示をしたいところ。

こういう時はWMIを使えばよいそうで、以下サンプルコード。
using System.Collections.Generic;
using System.Management;
(略)
Dictionary PortsTable = new Dictionary<string, string>();
ManagementClass mcW32SerPort = new ManagementClass("Win32_SerialPort");
foreach (ManagementObject aSerialPort in mcW32SerPort.GetInstances())
{
  PortsTable.Add(
    (string)aSerialPort.GetPropertyValue("Caption"),
    (string)aSerialPort.GetPropertyValue("DeviceID"));
}

comboBoxCOMPorts.Items.Clear();
foreach (string aPortCaption in PortsTable.Keys)
{
  comboBoxCOMPorts.Items.Add(aPortCaption);
}
GetPropertyValue("Caption")が画面表示用の詳細な名前を返してくれます。わざわざDictionaryオブジェクトで格納してるのは、この後ポートを開くときにはDeviceIDで指定しないといけないからです。

画面はシリアルポートのリストが取得できているという画面。下のテキストはAVR側からの応答で、受信もちゃんとできていることを確認。

ちなみに'Caption'や'DeviceID'といった文字列は、PowerShellで以下のコマンドを入力すると参考になります。
> Get-WmiObject -Class Win32_SerialPort
または
> $wmiser = new-object System.Management.ManagementClass "Win32_SerialPort"
> $wmiser.GetInstances()
[参考]

— posted by mu at 05:52 pm   commentComment [0]  pingTrackBack [0]

T: Y: ALL: Online:
ThemeSwitch
  • Basic
Created in 0.0270 sec.
prev
2024.11
next
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30