2014年 08月 17日

OpenGL で PBO 使ってテクスチャ更新するときの型式

なんかいまいちよくわからない (以下はgolangでのコードだけど、特にgolangに限らないはなし)

// PBO 作成
buffer := gl.GenBuffer()
buffer.Bind(gl.PIXEL_UNPACK_BUFFER)
gl.BufferData(gl.PIXEL_UNPACK_BUFFER, width*height*4, nil, gl.STREAM_DRAW)
buffer.Unbind(gl.PIXEL_UNPACK_BUFFER)

// テクスチャ作成
texture := gl.GenTexture()
texture.Bind(gl.TEXTURE_2D)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8_REV, nil)
texture.Unbind(gl.TEXTURE_2D)

以上のように初期化して、

texture.Bind(gl.TEXTURE_2D)
buffer.Bind(gl.PIXEL_UNPACK_BUFFER)
bitmap := *(*[]uint32)(gl.MapBufferSlice(gl.PIXEL_UNPACK_BUFFER, gl.READ_WRITE, 4))
// ... do something
gl.UnmapBuffer(gl.PIXEL_UNPACK_BUFFER)
drawBuffer.Unbind(gl.PIXEL_UNPACK_BUFFER)


buffer.Bind(gl.PIXEL_UNPACK_BUFFER)
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, fftBinSize, historySize, gl.RGBA, gl.UNSIGNED_INT_8_8_8_8_REV, nil)
buffer.Unbind(gl.PIXEL_UNPACK_BUFFER)

以上のように更新をしてる。

このとき、TexSubImage2D の引数にある gl.RGBA 及び gl.UNSIGNED_INT_8_8_8_8_REV を正しく(?)指定しないと余計に CPU 負荷がかかる。つまりGPUがネイティブに取り扱えるかどうかで決まると思うんだけど、何が GPU ネイティブなのかがよくわからない…

多少試した感じだと

  • gl.RGBA/gl.UNSIGNED_INT_8_8_8_8_REV
  • gl.BGRA/gl.UNSIGNED_INT_8_8_8_8_REV

の場合だけ高速に動作する。gl.RGBA/gl.UNSIGNED_INT_8_8_8_8 とかだとダメ。gl.RGBA は OpenGL ネイティブがこの型式だかららしいけど、gl.BGRA でもいけるのがよくわからない……

2014年 08月 16日

golang で websocket

websocket.JSON を使った場合

JSON をやりとりする場合専用の方法がある

(JSON-RPC ライクな実装を書いてみた場合)

package main

import (
	"fmt"
	"log"
	"net/http"

	"code.google.com/p/go.net/websocket"
)

type JSONRPCRequest struct {
	Method string      `json:"method"`
	Params []interface{} `json:"params"`
	Id     uint        `json:"id"`
}

type JSONRPCResponse struct {
	Id     uint        `json:"id"`
	Result interface{} `json:"result"`
	Error  interface{} `json:"error"`
}

type JSONRPCEventResponse struct {
	Result interface{} `json:"result"`
	Error  interface{} `json:"error"`
}

func main() {
	port := 51234

	http.Handle("/", websocket.Handler(func(ws *websocket.Conn) {
		log.Printf("New websocket: %v", ws)
		var req JSONRPCRequest
		for {
			if err := websocket.JSON.Receive(ws, &req); err != nil {
				break
			}
			log.Printf("Request: %v", req)
			res := &JSONRPCResponse{Id: req.Id}

			switch req.Method {
			case "echo":
				res.Result = req.Params
			default:
				res.Error = "unkown method"
			}
			log.Printf("Response: %v", res)
			websocket.JSON.Send(ws, res)
		}
		log.Printf("Closed websocket: %v", ws)
	}))
	log.Printf("websocket server listen: %d", port)
	err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
	if err != nil {
		panic(err)
	}
}

websocket.Message を使った場合

JSON だけやりとりするなら必要ないけどこのようにも書ける

  • websocket.Message.Receive(ws, &in)
  • websocket.Message.Send(ws, string(out))

をつかう。websocket.Message.Send の第2引数は byte[] だと JS 側では Blob を受けとれ、string だと JS 側では string を受けとれる。

バイナリを扱う場合、必然的にこちらをつかうことになる

同じことを冗長にやったコード

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"code.google.com/p/go.net/websocket"
)

type JSONRPCRequest struct {
	Method string      `json:"method"`
	Params interface{} `json:"params"`
	Id     uint        `json:"id"`
}

type JSONRPCResponse struct {
	Id     uint        `json:"id"`
	Result interface{} `json:"result"`
	Error  interface{} `json:"error"`
}

type JSONRPCEventResponse struct {
	Result interface{} `json:"result"`
	Error  interface{} `json:"error"`
}

func main() {
	port := 51234

	http.Handle("/", websocket.Handler(func(ws *websocket.Conn) {
		log.Printf("New websocket: %v", ws)
		var in []byte
		for {
			if err := websocket.Message.Receive(ws, &in); err != nil {
				break
			}

			log.Printf("Request: %s", in)
			req := &JSONRPCRequest{}
			json.Unmarshal(in, req)
			res := &JSONRPCResponse{Id: req.Id}

			switch req.Method {
			case "echo":
				res.Result = req.Params
			default:
				res.Error = "unkown method"
			}
			out, err := json.Marshal(res)
			if err != nil {
				panic(err)
			}
			log.Printf("Response: %s", out)
			websocket.Message.Send(ws, string(out))
		}
		log.Printf("Closed websocket: %v", ws)
	}))
	log.Printf("websocket server listen: %d", port)
	err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil)
	if err != nil {
		panic(err)
	}
}

KX3 の KY コマンド

KX3 はシリアル通信経由で直接 CW をエンコードして送信できる。つまりいわゆる専用の CW USB インターフェイスが必要がない。(RTTY も KX3 側でエンコードしてくれるので、これによって送信できる)

使うのは KY コマンドで

KY text;

の型式で送る。KY に続く空白を W にした場合、このコマンドで指定した文字列の送信が終わるまで、後続のコマンドの実行が保留される。(一旦キーの速度を変える場合に便利)

コマンドのリファレンスには「@ を送ると即時送信停止になるよ」というようなことが書いてあるが、これは K2 というリグの話で、KX3 ではあてはまらない。

KX3 では @ は常に @ のモールス符号が送出されるようになっていて、即時停止には RX コマンドを使う。RX コマンドは即時に送信を止めて受信モードに戻る (送信中符号の送信完了も待たない)

KX3 側のバッファはあまりないみたいだが、TB コマンドで今何バイトバッファに入っているかを知ることができる。ただ、9文字以上だと常に9になってしまうので、正確にはわからない。

この機能を使って PC キーイングを作る場合以下のようになりそう

  • TB コマンドを頻繁にポーリングして状態更新しつづける
  • 送信したい文字列はバッファにいれ、5文字程度までで分割して KY 実行
  • 残り2文字程度になったらバッファから残りを KY

KX3 側に保持されているバッファの内容を取得することはできず、残り送信文字数だけしかわからない。なので同期が正確にできないと厳しい。この機能は送信中にコマンド実行する必要があるので RFI が比較的でやすく信用できるかというと微妙な気がする。

とりあえず既に USB CW インターフェイスを作ったときの UI があって、WebSocket の互換サーバーさえ書ければ動かせるので、試してみる。

2014年 08月 15日

KX3 用の Mac OS X バンドスコープ (Panadaptor) 実装を golang で

できたもの

できたものはこんな感じのです。

前提

KX3 にはオーディオ帯域までダウンコンバージョンされた信号が直接でる出力端子があり、一般的なステレオ音声入力を持つコンピュータを使ったソフトウェア解析により広帯域の無線信号を復調できる。

なのでこの信号を使って MacBook 上で FFT して広域の時系列データをウォーターフォール表示し、信号を高速で見つけられるようにしたい。

具体的には Windows においては OmniRig 及び HDSDR によって実現できることを Mac で行いたい。

問題

OmniRig と HDSDR は Mac 上に VM を立てて Windows を動かせば当然動くが、残念ながらパフォーマンスがあまりでない。特に CPU 使用率が非常に高くなってエコではない。

wine によるエミュレーションを行ってもある程度動かすことができるが、自分の環境では KX3 とのシリアル通信に難があった (書きこみはできるが読みこみができない) また、この方法も CPU リソースを大量に使う。

このため、Mac OS X 上でネイティブに動く似たようなソフトウェアが必要だと感じた。

最初の目標

  • FFT してウォーターフォール表示する
  • KX3 とシリアル接続し、周波数を相互に同期する

コンピュータ側で復調したりする機能は今回特に必要性を感じなかったので大変シンプルな要件

また、できれば KX3 側の局発を変えても、ウォーターフォール表示の履歴をスムーズに繋げたいと思った (既存の SDR ソフトだと必ず局発を中心にウォーターフォールが表示されてしまうため、時系列データがずれて表示されてしまう)

実装

今回実装言語として go を採用した。以下の理由がある

  • 最近流行っててかっこよさそうだから
  • ナイーブに書いてもそこそこ早いという噂があったから

音声入力まわりは portaudio、グラフィックス表示まわりは OpenGL を使って実装した。

FFT ウォーターフォール

入力の I/Q 信号は局発の位相を90度ずらしてコンバージョンした直交信号になっており、これを複素数として FFT すると、正の周波数と負の周波数を一発で解析できる。今まで FFT しても実数しか扱ったことがなく負の周波数部分は捨てたことしかなかったが初めてちゃんと使った。

基本、表示側はこれをうまいこと表示しているだけでおわってる。バッファは container/ring の ring.Ring でリングバッファとしていて、解析済みのビットマップを1行そのまま保持している。

シリアル通信

goserial というライブラリがあるので基本これを使ってやるだけだけど、いろいろとハマる。

  • FTDI のライブラリが不安定でよく kernel panic になる (強制再起動)
    • どうも read でブロックしているときに close しようとすると close も block し、そのときプロセスを無理矢理 kill させると Mac 全体が落ちる、ということがわかった。もしプロセスが block しているようなら、USB を物理的に抜くと read に EOF が返り、close も成功させることができる
  • KX3 は SET のコマンドにレスポンスを返さない
    • FA00007100000; とだけ送っても、それが成功したのかどうかはわからない。
    • FA00007100000;FA; として必ずレスポンスを返すようにして解決

go に慣れていなくて、make(chan T) し忘れたせいで一晩ハマったりもした (なぜか1度だけ chan 通信が動くという謎挙動になった)。

結果

当初の目的はひとまず果たすことができた。

  • MacBook Pro Retina, 13-inch, Late 2013 (2.4GHz Core i5 / 16GB メモリ / Intel Iris) において CPU は 30% 未満
    • もっと減らしたいけど、とりあえずまぁまぁいい感じ
    • GL の表示はかなりスムーズ
    • 処理が遅すぎると音声の入力バッファがあふれるので頑張る必要がある
  • バンド内でどれぐらい局が出ているか一見でわかりやすくなった
  • クリックしてすぐその信号を聞けるので捗る

40m (7MHz) 帯を見ながら実験してたけど、このバンドは終始賑やかというのが可視化されて面白かった。逆に、他のバンドはいつ見てもうちのロケーションでは殆ど聞こえないということもわかった。悲しい事実も可視化された。

あと気になっているのは、0Hz付近に常に高いスペクトルがでてしまうことだけど、よくわかってない。3000円ぐらいの安いサンプリングデバイスを使っているせいかもしれないけど、良さげなのは結構なお値段なのできつい。

Steinberg 2x2 USB 2.0 オーディオインターフェース UR22 - Steinberg(スタインバーグ)

Steinberg(スタインバーグ)

3.0 / 5.0

これが欲しいです。

2014年 08月 12日

Elecraft KX3 Bottom Keyer Paddle Pin assign

"KXPD3 Keyer Paddle" は普通の KEY 端子とは別に、下部で4ピンで出ているのを使っている。マニュアルにも回路図にもピン配置が書いていないので調べた。(KXPD3 は持ってない)

ピン番号は回路図に倣った。KX3SchematicDiagramDec2012.pdf の9ページ

3V3 は何のためについているかわからないけど、330Ωがついているので10mAしか流れない。実測したけど9mAしか流れない。

GND はケースに導通しているのと共通

2014年 08月 07日

MacRuby でスクリーンキャスト用のキー履歴表示ツールを作る

よくあるツールなんだけど、なかなか希望に叶うものというと見つけにくく、どうせなら自分で書いたらいいかと思ったので書いてみた。やってみたら割とすぐ書けた。

MacRuby のインストールが必要。1ファイルにしたかったので、XCode なしで使っている。あんまり XCode なしでの作例がないが普通に NSApplication.sharedApplication を取得したらいいだけだった。

NSEvent.addGlobalMonitorForEventsMatchingMask:handler: は「システム環境設定」→「セキュリティとプライバシー」→「アクセシビリティ」で macruby を許可しないと使えない。これができるということは、すなわちキーロガーが実装できるということなので、必要なときだけ許可するほうがいいと思う。

#!macruby

framework "Cocoa"

class MainView < NSView
	def init
		super
		@log = ""
	end

	def drawRect(rect)
		super
		NSColor.clearColor.set
		NSRectFill(bounds)

		font = NSFont.boldSystemFontOfSize(24)

		shadow = NSShadow.alloc.init
		shadow.setShadowColor(NSColor.blackColor)
		shadow.setShadowBlurRadius(2)
		shadow.setShadowOffset([0, 0])
		attrs = NSMutableDictionary.alloc.initWithDictionary({
			NSForegroundColorAttributeName => NSColor.whiteColor,
			NSFontAttributeName            => font,
			NSShadowAttributeName          => shadow,
		})

		y = 0
		@log.split(/\n/).reverse.each do |line|
			storage   = NSTextStorage.alloc.initWithString(line, attributes: attrs)
			manager   = NSLayoutManager.alloc.init
			container = NSTextContainer.alloc.init

			manager.addTextContainer(container)
			storage.addLayoutManager(manager)

			range = manager.glyphRangeForTextContainer(container)
			10.times do
				manager.drawGlyphsForGlyphRange(range, atPoint: [0, y])
			end
			rect = manager.boundingRectForGlyphRange([0, manager.numberOfGlyphs], inTextContainer: container)
			y+= rect.size.height
		end
	end

	def <<(log)
		@log << log
		@log = @log.split(/\n+/, -1).last(5).join("\n")
	end

	def clear
		@log.clear
	end

end

class AppDelegate
	attr_accessor :window
	def applicationDidFinishLaunching(a_notification)
		@enable = true

		prevKeyed = 0
		unless AXIsProcessTrusted()
			$stderr.puts "Require setting"
			system('open', '/System/Library/PreferencePanes/Security.prefPane')
			exit
		end

		NSEvent.addGlobalMonitorForEventsMatchingMask(NSKeyDownMask, handler: lambda {|e|
			return unless e.type == NSKeyDown

			mod = ""
			if e.modifierFlags & NSShiftKeyMask != 0
				mod += "⇧"
			end

			if e.modifierFlags & NSControlKeyMask != 0
				mod += "⌃"
			end

			if e.modifierFlags & NSAlternateKeyMask != 0
				mod += "⌥"
			end

			if e.modifierFlags & NSCommandKeyMask != 0
				mod += "⌘"
			end

			if (e.modifierFlags & NSControlKeyMask != 0) && (e.modifierFlags & NSCommandKeyMask != 0) && e.charactersIgnoringModifiers == 'l'
				@enable = !@enable
				@view.clear
				@view << (@enable ? "[enabled]" : "[disabled]")
				@view.needsDisplay = true
				return
			end

			if @enable
				if e.modifierFlags & (NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask) == 0
					char = readable(e.characters)
					if Time.now.to_i - prevKeyed > 1
						@view << "\n#{char}"
					else
						@view << char
					end
				else
					@view << "\n#{mod}#{readable(e.charactersIgnoringModifiers).upcase}\n"
				end
				@view.needsDisplay = true
			end

			prevKeyed = Time.now.to_i
		})
		rect = [0, 0, 800, 500]
		@window = NSWindow.alloc.initWithContentRect(rect, styleMask: NSBorderlessWindowMask, backing: NSBackingStoreBuffered, defer: 0)
		@window.opaque = false
		@window.hasShadow = false
		@window.level = 1000
		@window.movableByWindowBackground = true
		# @window.ignoresMouseEvents = true
		@window.makeKeyAndOrderFront(nil)
		@window.orderFrontRegardless

		@view = MainView.alloc.initWithFrame(rect)
		@view.init
		@view << "Initialized"

		@window.contentView = @view
	end

	REPLACE_MAP = {
		"\r"   => "↵\n",
		"\e"   => "⎋",
		"\t"   => "⇥",
		"\x19" => "⇤",
		" "    => "␣",
		"\x7f" => "⌫",
		"\x03" => "⌤",
		"\xEF\x9C\xA8" => "⌦",
		"\xEF\x9C\x84" => "[F1]",
		"\xEF\x9C\x85" => "[F2]",
		"\xEF\x9C\x86" => "[F3]",
		"\xEF\x9C\x87" => "[F4]",
		"\xEF\x9C\x88" => "[F5]",
		"\xEF\x9C\x89" => "[F6]",
		"\xEF\x9C\x8A" => "[F7]",
		"\xEF\x9C\x8B" => "[F8]",
		"\xEF\x9C\x8C" => "[F9]",
		"\xEF\x9C\x8D" => "[F10]",
		"\xEF\x9C\x8E" => "[F11]",
		"\xEF\x9C\x8F" => "[F12]",

		"\xEF\x9C\x80" => "↑",
		"\xEF\x9C\x81" => "↓",
		"\xEF\x9C\x82" => "←",
		"\xEF\x9C\x83" => "→",
		"\xEF\x9C\xAC" => "⇞",
		"\xEF\x9C\xAD" => "⇟",
		"\xEF\x9C\xA9" => "↖",
		"\xEF\x9C\xAB" => "↘",
	}

	def readable(char)
		p char
		re = Regexp.new(REPLACE_MAP.keys.map {|i| Regexp.escape(i) }.join("|"))
		char.gsub(re, REPLACE_MAP)
	end
end

app = NSApplication.sharedApplication
app.delegate = AppDelegate.new
app.run
2014年 08月 03日

今 (物理的に) 半導体が熱い!!! (電子負荷)

電子負荷

電源のテストを行いたいときは、適当な抵抗を繋いだりするわけだが、特定の抵抗値を狙ってつくるのはめんどうくさく、また許容損失が大きいものはつくりにくい。

そこでパワーバイポーラトランジスタやパワーFETを使って可変抵抗にする、というのが電子負荷らしい。

仕様

電子負荷には定抵抗モード・定電流モード・定電力モードなどいろいろあるが、今回は面倒なので定電流モードのだけを考えてある。

  • 14V 3A (42W) ぐらい流したい
  • 温度によりシャットダウンをつけたい
  • 簡易電流・電圧・電力計をつけたい

完成

とりあえずこんな感じになった。

回路図

試行錯誤

メインのパワーFETは2SK1122に

  • 25℃時の許容損失が100W
  • エンハンスメント型かつ Vth が低い
  • 1個150円ぐらいで割と安い

許容損失は温度上昇に伴なって下がっていくが、このFETの場合 60℃ぐらいでは70W、90℃までいくと50Wになる。

放熱が必須なのでとりあえず安いCPUクーラーを買った。1000円もしないが PWM 制御のファン付きでお得

実験1

まず単体の 2SK1122 のゲートに可変抵抗で分圧した電圧を加えつつ、放熱器に貼りつけて 12V 2A 程度まであげつつ流してみた。

指で触れられないぐらい熱くなるということがまずわかったので、ちゃんと温度を測りながらにするため、直読温度センサーも一緒に貼りつけて温度を実測しながらに切り替えた。

FET単体でも定電流素子として使われることがある通り、これでもある程度は安定して流れてくれる。ただ、FET が定電流なのは温度が変わらないことや、負荷電圧が変化しないことが条件なので、温度上昇に伴なって電流量は増えていってしまい、電流量が増えるとさらに発熱するという、ある種の暴走状態になる。この実験では放熱器をつけて、電源側で最大2Aに制限しているので、65℃ぐらいで安定する感じだった。

実験2

電流制限を安定させるため、オペアンプのフィードバックをつけた。オペアンプは単電源でつかえて安い LM385 にした。動作が遅いので発振もしにくそう。

オペアンプの入力の +/- は常に同じ電位になるように調整される (バーチャルショート/イマジナリショート)

+ に入れた電圧と、抵抗上部の電圧が等しくなるようにオペアンプの出力が自動でいい感じに変化するという挙動になるため、+ に入れた電圧と抵抗の比 (I=V/R) にのみ依存する形で定電流動作をするようになる。いろいろ別に抵抗がついているのは動作安定のため (FETの入力の抵抗はオペアンプの出力電流を制限するため・帰還の抵抗は発振防止)。

入力側の分圧抵抗によってレンジを決めている。入力する最大電圧を設定することで最大電流を決められる。

実験3

放熱器に素子をもっと押しつけるといいみたいな話を聞いたので、FETまわりだけケースに取り付けることにした。CPU クーラーなのでプッシュピンがついており、固定にはこれを使える。だいたい 75mm の幅でM4の穴をあけると丁度いいみたい。

ただ、これだけだとFETがケースにちゃんと接触せず、圧力がかからないので、適当なナットを貼りつけて厚さを増やしている。

この状態で 2A 12V かけると 55℃程度で安定する。13.8V 5A かけると90℃ぐらいで安定する (69W 90℃なので許容損失はオーバーだけど)

その他実装

コントローラはAVRで、以下のような機能を実装してある

  • 温度によってファン速度の変更 (ヒステリシスに動く)
  • 電圧計・電流計・電力計
    • ボタンで切り替え
  • 現在温度からFETの許容損失を計算し、一定の範囲内から超えたことが観測された場合強制的に停止する
    • 強制的に温度表示に切り替わって点滅することで状態表示

表示は4桁の7セグを1セグごとにダイナミック点灯させている。1桁ずつ表示するのが普通っぽい?けど、部品を増やしたくなかったので、同時に電流が流れすぎないようにした。

やばそうなとき強制停止する機能をつけたかったので、オペアンプの入力用の電圧はAVRのポートから出している。ただ、あんまりこれは電圧が安定していないので、シャントレギュレータで基準電圧をつくってからボリュームで分圧してる。上側のボリュームは半固定で、最大値を調整するためにつけてる。実際操作するのは下側のボリュームになってる。

2014年 07月 31日

バランの役目・平衡とは何なのか

いまいちイメージがしにくくて理解したとはいいきれてない部分の覚書

平衡経路は差動信号、すなわち位相が反転した信号を2線に乗せ、接地せずに伝送する。

対して不平衡経路はシングルエンド、すなわり片方を接地させて信号を伝送する。

信号伝送の形式が違うので、当然直接接続してはいけない。直接接続した場合、不平衡側は GND を基準としているので、GND の変動はすなわち信号線にもGNDが変動しただけの変動が発生するということであり。これはコモンモードのノイズということになる。

バランによって平衡・不平衡を接続することができる。

電圧バラン(トランス)の場合、差動信号の入力は出力からすると単に信号がおおきくなったようにみえる。GND は絶縁されており、コモンモードは発生しない。

Uマッチというバランの場合、片方の伝送路を1/2λ遅延させることによって位相を反転させて逆相を得て平衡信号を作りだす

電流バランの場合、GND変動によって発生するコモンモード電流を遮断する形で機能する。性質上完全に遮断しにくいが、ロスも少ない。

2014年 07月 28日

JSDeferred -> Angular $q 置き換え方法

AngularJS には $qっていう promise の枠組みがあるので、使っておくといいこと (ビューが自動的に更新されるだけだけど) がある。フレームワーク組込みの仕組みがあるのに別途 Deferred の仕組み、しかも thenable(笑) じゃない(笑) JSDeferred を読むのもバカにされると思うので、以下のように JSDeferred から Angular $q へ置き換える方法を記す。

基本

JSDeferred における global な next() 関数を $q.when().then() に置き換え、Deferred#next を then() に置き換えればだいたい動く

next(function () {
    alert(1);
    return next(function () {
        alert(2);
    }).
    next(function () {
        alert(3);
    });
}).
next(function () {
    alert(4);
});

こういうのを、こう

$q.when().then(function () {
    alert(1);
    return $q.when().then(function () {
        alert(2);
    }).
    then(function () {
        alert(3);
    });
}).
then(function () {
    alert(4);
});

parallel() は?

$q.all() を使え

loop() は?

頑張って書く。いろいろやりかたはあると思うけど、例えばこう

$q.when().then(function () {
	var list = [1, 2, 3], sum = 0;
	return $q.when().then(function loop () {
		if (list.length) {
			return $q.when(list.shift()).then(function(item) {
				console.log('item', item);
				sum += item;
			}).then(loop);
		} else {
			return sum;
		}
	});
}).
then(function (result) {
	console.log(result);
});

wait() は?

setiTimeout で頑張って書く

2014年 07月 25日

自作 USB CW キーイングデバイスを作る

去年AVR で USB 接続の PC キーヤーを作るということをやったのだけど、結局ちゃんと形にはせず放置してしまっていた。最近なんとなく自分の中で PC キーイングの機運が高まってきたので、まじめに安定したものを作ろうと頑張って、ある程度成果がでてきた。

当然似たようなデバイスは既にあるので、改めて作る必要はないんだけど、自分で作れそうなものは、一回ぐらい自分で作りたいものですね。

ハードウェア

安定して動くように試行錯誤した結果、USB のデータラインに 100pF のパスコン (ノイズ対策)、リセットピンを外部プルアップ (USB のデータラインに電流が多く流れて、リセットされやすくなるので)、USB ラインのツェナーダイオードをちゃんと計ってから使う、18MHz の水晶 (CRCチェック用) とかになった。あとはもともとと同じだと思う。

ファームウェア

USB まわりを割と丁寧に実装しなおした。UI との整合性をとるため、機能をちょいちょい足している。usbFunctionWrite で -1 を返すと STALL の意味になるとか、V-USB のドキュメントをよく読んだほうがいい。

ソフトウェア

ドライバをカーネルレベルで書くのはデバッグが大変で嫌なので、最初から libusb 関係のものを使うことしか考えてなかった。最初は Chrome App から直接 chrome.usb で扱おうとしたのだが、いろいろあってやめて、ruby + libusb + em-websocket で WebSocket サーバを書いて中継している。

libusb の同期的インターフェイスは、実際のところ非同期インターフェイスのラッパーになっており、マルチスレッド環境で使うとレースコンディションが発生することがある。libusb のドキュメントにいろいろ書いてあるが、面倒なので ruby 側で mutex のロックをかけるようにしたら解決したので深く追ってない。

また、ホットプラグ対応もなんか刺さったりしてつらいのでやめて、デバイスが接続されていないときは定期的にポーリングするというクソっぽいけど正確に動く実装にした。

インターフェイス

そして WebSocket で通信するページをペライチで作って試してている。全部込みで動画にしてみた。

今後

まずは無闇にストールしたり、刺さったりしないという基本的な部分で安定することを目指して頑張った。「もう無理では……」と思ったこともあったけど、いつのまにか結構安定した。ただ、実際の運用まで行ってないので、インターフェアにどれぐらい耐性があるかはわかってない。試験電波を出してオシロで信号ラインを見た感じだと大丈夫そうだけど、よくわからない。

手持ちのユニバーサル基板に組んだので、作ってみたらケースを含めちょっと大きくなってしまった。内容的にはたいしたことがないので、フリスクケースに収まるような基板を作ってみたい。

あとは、ログツールとの連携をしたいと思っているけど、ログツールを作りなおしているので、まだまだ先になりそう。