電子負荷

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

そこでパワーバイポーラトランジスタやパワー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のポートから出している。ただ、あんまりこれは電圧が安定していないので、シャントレギュレータで基準電圧をつくってからボリュームで分圧してる。上側のボリュームは半固定で、最大値を調整するためにつけてる。実際操作するのは下側のボリュームになってる。

  1. トップ
  2. tech
  3. 今 (物理的に) 半導体が熱い!!! (電子負荷)

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

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
  1. トップ
  2. tech
  3. MacRuby でスクリーンキャスト用のキー履歴表示ツールを作る

移動用のオールモードトランシーバーが欲しい

と思いはじめ、いろいろ調べてみた。移動といっても車も自転車もないので、バッテリーやアンテナなども含め徒歩や公共交通機関で運搬可能な範囲でなければならない。

国内だと FT-817ND というリグがこの用途ではスタンダードなようでかなりたくさんの人が使っている。FT-817ND は2001年発売とかなので、おそろしく長く売ってる無線機となっていて、国内には競合製品がない。

ほかにも候補があるか?と思ってさらに調べてみると、アメリカ Elecraft 社の KX3 というのが良さそうだということがわかった。こちらは2012年?ごろに発売された比較的新しい無線機で、FT-817ND と同じぐらい小さい。

結論からいうと表題の通り KX3 を買ったが、以下のポイントで決めた

  • 変調/復調を全部DSPでやってる
    • クラシックなスタンドアロン無線機の外観だが中身は完全にSDRでかっこいい
    • I/Q信号 (直交信号) を出力する端子があるので、サンプリングデバイスさえあればPC SDRも可能
  • 消費電力がFT-817NDに比べて多少低い (特に待機電力は低い)
    • 移動に使いたいので少しでも効率が良いと嬉しい
  • 軽い
  • ファームウェア更新の頻度が高い
    • ちゃんとメンテされており、なおかつ更新して改善できるほどの柔軟性がある

問題は主にコスト面で、FT-817ND がフィルタ込みで9万円ぐらいで買えるのに対し、KX3 は必要なオプションを入れていくと1.5倍ぐらいになる。また、技適を取得していない無線機なので、局免をとるのに結構ハードルがある。

届くまで

特に何も考えず Elecraft に直で注文した。

  1. JSTで木曜日の午前中に発注 USPS Priority Mail Express International
    1. オーダー確認メールがすぐにくる
    • なんか他の人だとこのメールに返信してる人がいるけど、ただの確認なので返信する必要ないと思う
  2. JSTで金曜日の早朝に発送
    1. PST だと木曜日の午後なので、2営業日以上かかると書いてあったけどかなり早く発送された
  3. カリフォルニア州フリーダム → サンノゼ → サンフランシスコと移動
  4. 金曜日のうちに Processed Through Sort Facility / ISC SAN FRANCISCO (USPS) になる (サンフランシスコ国際空港から発送の状態)
    1. PST では金曜早朝
  5. その日のうちに航空便に乗るかなと思ったが乗らず5日(3営業日)経過
  6. 翌週、JST 火曜日の夜に東京国際郵便局に着
    1. USPS側ではステータスは更新されず、JP (Japan Post) 側だけ先に更新
  7. 水曜日午前中、通関手続中に
  8. 2時間ぐらいで次の通関手続中
  9. 木曜日午前中に国際交換局から発送に
  10. 金曜の早朝に配達店到着
  11. 金曜の昼ごろ到着

国外にあるうちは USPS のほうが細かくステータスがアップデートされるけど、国内に入った瞬間からJPのほうが早く更新されるようになるみたい。2時間ぐらい遅れてUSPSにも反映される。


オプションや価格

モジュールキットと完成品とがあるんだけど、せっかくなのでモジュールキットにした。地味にこういうの苦手なのでちょっと心配なんだけど、Elecraft の About ページ 見てたらキットにしなければならない気がしてきたのでそうした。

ケーブルセットは案外 L 型のコネクタが手に入りにくいので買っておいた。ルーフィングフィルターは自分の用途だとたぶん必要ないけど、CW メインでやるという意気込みをもって追加した (あとから追加すると調整が面倒)。アンテナチューナーはワイヤーアンテナでもいける感じのやつなので、最悪長いケーブルさえあれば出れるように追加した。

  • KX3-K KX3 160-6 M Xcvr (Modular Kit)
  • KX3-PCKT Accessory Cable Set
  • KXFL3 dual-Passband Roofing Filter
  • KXAT3 Internal, 20-W Automatic Antenna Tuner

なかなかかかる。無線機に関税はかからないけど、消費税は税関でかかる。

注文しちゃったあとで調べたら国内代理店だと、ちょうど代理店の手数料分1万ちょいだけ浮く感じだった。直販だと届くまでに日数がかかるのと、ちゃんと届くのが不安とかがあるので、代理店で買ってもいいレベルの差ではあるかもなあという印象。

組立

思いのほか小さい箱で届く。

検品からはじめて、最初に動かすまで4時間ぐらいかかってしまった。めちゃくちゃ難しいみたいなことはないんだけど、小さいだけあって中身が狭いので、ビス締めるのが微妙にむずかったり、ケーブルの接続がしにくいとかがあった。とりあえずちゃんと動いたのでよかった。

ビスの長さが一部 (スピーカーまわり) で違ったんだけどなんとかなったっぽい。アメリカンな感じ…

ファームウェアはなぜか最初から最新 (2014-07-11) の beta 版 (production 版と別れてるんだけど) が書きこまれていたのでアップデートはしていない。

  1. トップ
  2. ham
  3. Elecraft KX3

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

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

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

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

  1. トップ
  2. tech
  3. Elecraft KX3 Bottom Keyer Paddle Pin assign
  1. トップ
  2. ham
  3. Elecraft KX3 Bottom Keyer Paddle Pin assign

15時ぐらいから悪寒がしたが MTG が連続で入っていたのでとりあえず出てから帰った。一晩で治るかなと期待したがそんなことなく1日休み

最近多いなと思ったので日記検索した

  1. トップ
  2. 体調不良
  3. 熱がでて一回休み

できたもの

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

前提

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 -

3.0 / 5.0

これが欲しいです。

  1. トップ
  2. tech
  3. KX3 用の Mac OS X バンドスコープ (Panadaptor) 実装を golang で
  1. トップ
  2. ham
  3. KX3 用の Mac OS X バンドスコープ (Panadaptor) 実装を golang で

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)
	}
}
  1. トップ
  2. tech
  3. golang で websocket
  1. トップ
  2. golang
  3. golang で websocket

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 の互換サーバーさえ書ければ動かせるので、試してみる。

  1. トップ
  2. tech
  3. KX3 の KY コマンド
  1. トップ
  2. ham
  3. KX3 の KY コマンド

Mac はかっこいいんだけど、無線機とかのボタンとツマミがいっぱいあるかっこよさとは方向が違うんだよな。

スタイリッシュであることは翻っておもねり的ダサさも感じさせるから、そうじゃないボタンとツマミにまみれたナードデバイスというのは安定のかっこよさを持ってる。

なんかいまいちよくわからない (以下は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 でもいけるのがよくわからない……

  1. トップ
  2. tech
  3. OpenGL で PBO 使ってテクスチャ更新するときの型式

アース製薬 アースノーマット 蚊取り黒ブタ 60日セット (器具+45mL) -

5.0 / 5.0

これを寝室とリビングの1つずつ買った。黒いブタバージョンで可愛い。電源ケーブルが「しっぽ」になっているのも地味にいいデザインだなあと思う。昔ながらの陶器のブタの蚊取り線香って僕は現実で見たことがないけど、それでもブタの形が「蚊取り線香」をイメージさせるというのは、なんかおもしろい。

アースノーマットって、動いている気配が一切ないのでちょっと不安だけど一応効いてるみたい。

golang で JS 的な addEventListener/dispatchEvent 的なことをしたいときどうするか?

emission

JS にあるようなのと全く同じ様に「イベント名」でイベントの種類を識別して任意の「イベントオブジェクト」をやりとりする。

On/Off/Once とか jQuery にあるような便利メソッドがついてる。

リフレクションで型変換は隠蔽されているが、もしリスナーとエミッターとで型が食い違っていると、実行時エラーになる。

package main

import (
	"github.com/chuckpreslar/emission"
	"log"
)

type ClickEvent struct {
	button string
}

type KeyEvent struct {
	key string
}

func main() {
	emitter := emission.NewEmitter()
	emitter.On("click", func (ev *ClickEvent) {
		log.Printf("onclick %v", ev)
	})
	emitter.On("key", func (ev *KeyEvent) {
		log.Printf("onkey %v", ev)
	})

	emitter.Emit("click", &ClickEvent{ button : "left" })
	emitter.Emit("click", &ClickEvent{ button : "right" })
	emitter.Emit("key", &KeyEvent{ key : "A" })

	// panic
	emitter.Emit("key", &ClickEvent{ button : "right" })

	// or also panic
	emitter.On("key", func (ev *ClickEvent) {
		log.Printf("onkey2 %v", ev)
	})
	emitter.Emit("key", &KeyEvent{ button : "right" })
}

go-pubsub

Pub/Sub/Leave というメソッドが生えている。Pub は Emit/Dispatch, Sub は addListener/On, Leave は removeListener/Off に対応する。

「イベント名」というパラメータが存在せず、型の選択によって自動的にディスパッチされる。よって型がマッチしなければリスナーは実行されないので、原理的に型変換の実行時エラーは発生しない (もし間違えた場合単に実行時になって「呼ばれない」ことに気付く)

package main

import (
	"github.com/mattn/go-pubsub"
	"log"
	"time"
)

type ClickEvent struct {
	button string
}

type KeyEvent struct {
	key string
}

func main() {
	ps := pubsub.New()

	ps.Sub(func(ev *ClickEvent) {
		log.Printf("onclick %v", ev)
	})
	ps.Sub(func(ev *KeyEvent) {
		log.Printf("onkey %v", ev)
	})

	ps.Pub(&ClickEvent{button: "left"})
	ps.Pub(&ClickEvent{button: "right"})
	ps.Pub(&KeyEvent{key: "A"})

	time.Sleep(100 * time.Millisecond)
}

サンプルコード書いてて気付いたが、Sub に登録した関数は Sub を呼んだ順あるいは Pub を呼んだ順に関係なく実行されうる? (ref. 登録された関数を go で呼んでる)

下記コードのように time.Sleep の代わりに Pub/Sub によって終了を待とうとするとたまに失敗する。(完全に終了を待つ方法がわからなかった)

package main

import (
	"github.com/mattn/go-pubsub"
	"log"
)

type ClickEvent struct {
	button string
}

type KeyEvent struct {
	key string
}

func main() {
	ps := pubsub.New()

	ps.Sub(func(ev *ClickEvent) {
		log.Printf("onclick %v", ev)
	})
	ps.Sub(func(ev *KeyEvent) {
		log.Printf("onkey %v", ev)
	})

	ps.Pub(&ClickEvent{button: "left"})
	ps.Pub(&ClickEvent{button: "right"})
	ps.Pub(&KeyEvent{key: "A"})

	// waiting for processing all messages
	// -> FAIL
	done := make(chan bool)
	ps.Sub(func(b bool) {
		ps.Close()
		done <- true
	})
	ps.Pub(true)
	<-done
}
  1. トップ
  2. tech
  3. golang で event emitter/dispatcher 的なもの

バンドスコープ を作ったのはいいが、41.1kHz サンプリングだし、0Hz付近にUSB オーディオデバイス由来の強力なノイズが入ってるのがイケてないので、良さげな192kHz サンプリングのUSBオーディオデバイスが欲しくなった。

SDR 用サウンドカードのまとめ みたいなページを見てみたけど、現状手に入るなかでよさそうなのは Steinberg UR22 というのしかない。これも注釈がついてて「ノイズがあるよ」って書いてあるけど、HFでは問題にならなそうとのことなので、買ってみた。

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

3.0 / 5.0

独自のドライバが必要で (UAC2 とかじゃない) 嫌な感じだけど、普通に Mac 用の提供されている (Yamaha Steinberg Driver とかでググる) のでそこらへんではハマらなかった。

しかしこれ、用途的に宅録みたいなの向けなので、入力や出力がちょっとややこしい。入力はXLR(キヤノンコネクタ)バランス入力・3極標準プラグ (ステレオ標準プラグ = 6.3mm) バランス入力・2極標準プラグ (モノラル標準プラグ = 6.3mm) アンバランス入力といろいろ対応されている。KX3 の出力は 2.5mm ステレオジャックでこれはアンバランスなので、2.5mm ステレオジャックからモノラル標準ジャック2本へ変換が必要になる。

2.5mm ジャック -> 3.5mm プラグへの変換は KX3 注文時に一緒に買ってあるので、以下のように3.5mmジャック・ジャックと、3.5mm プラグ → 6.3mm プラグ×2 の変換を買った。

audio-technica ラインケーブル ATL462A/1.5 -

4.0 / 5.0

audio-technica GOLD LINK Fine プラグアダプター ステレオミニ延長 AT509CS -

4.0 / 5.0

入力なしのとき

ちょっとノイズが立ってるところがあるけど、全域でだいぶノイズが少なくなった。中心周波数付近のノイズが皆無になったのはデカい…

±30kHz 付近のノイズは電源のスイッチングノイズで、直接オーディオインターフェイスに飛びこんでいるっぽい。それ意外の 60kHzぐらいから90kHz までのノイズはUSBインターフェイスのノイズっぽい。

入力いれたとき


両端に向かってゲインが下がっていくのは KX3 のマニュアルにも書いてあって、KX3 出力時点でこうなってしまう。FFT したあと補正するのがいいと思うけど、まだやってない。

The RX I/Q outputs from a receiver are not “flat” over an infinite frequency range; the signal-conversion process results in some slope (decrease in gain) as you move farther from the center frequency. In the case of the KX3, the signal will be reduced by about 2.5 dB at +/- 24 kHz, 4 dB at +/- 48 kHz, and 7 dB at +/- 96 kHz. The spectrum amplitude on the display, including the apparent noise floor of the receiver, will “roll off” by these amounts.

KX3 Owner's man Rev B4

192kHz の範囲が一望できるのがなんかすごい広くなる。

96kHz サンプリングだとあんまり気にならない範囲になる。狭くなるけど CW の場合 96kHz ぐらいで見たほうが選局しやすい気がする…

だいぶいい。

  1. トップ
  2. tech
  3. 192kHz サンプリングのUSBオーディオデバイス
  1. トップ
  2. ham
  3. 192kHz サンプリングのUSBオーディオデバイス
  1. トップ
  2. SDR
  3. 192kHz サンプリングのUSBオーディオデバイス

ウォーターフォール表示で遊んでいて、アマチュア無線のバンド全域に繰り返し強いノイズが入っていたり、部分的に超強力なノイズが入っていたりすることがあって、気になっていた。

少し自分の周辺機器をみまわして電源を切ったり入れたりしたらかなり変わったのでメモしておく

主要機器を切った状態

切れそうな機械の電源をだいたい切った状態


すぐ電源切れないデバイスも結構あるけど、かなり静かになった。

エアコン

規則的なノイズが全域に入る。エアコンはどのバンドでもノイズが入るけど、14MHz帯が一番ひどい。

空気清浄機

ダイキンの空気清浄機の電源を入れるとエアコンと似たようなノイズがかなり強く全域に入る。

モーターのブラシノイズなんだろうか? よくわからない。放電ノイズっぽくはないけど

スイッチング電源のノイズ

特定の周波数ごとに強力なノイズが入る。負荷に応じてノイズが揺らぐのが特徴っぽい。7MHz や 10MHz あたりが一番ひどい。ハイバンドになるほど強度が下がっていく。

蛍光灯のノイズ

14MHz帯に入っていた。画像はつけた直後で、なんか周波数を変えながら「ログインしてきました!」って面してるのが蛍光灯ノイズ。

これとは別に蛍光灯のデスクライト(インバーター制御)もあって、それはそれでこれとは違うノイズの入りかたをする。デスクライトのほうがだいぶ広範囲でひどい。

その他

USB HDD か、その電源アダプタ(スイッチング)かがノイズを出していたが電源ケーブルにコアを巻いたら、これはかなり改善した。HDD 側のノイズが電源ラインに戻ってたのかな?

今回、

  • 扇風機
  • MacBook Proの電源アダプタ

はノイズ源ではなかった。

  1. トップ
  2. tech
  3. 身近なノイズ源を特定する
  1. トップ
  2. ham
  3. 身近なノイズ源を特定する

ノイズ源の特定までやったはいいが、うまい対策をうたなければならない。

このノイズ源特定のときは、とりあえず電源ラインに内径1cmぐらいのパッチンコアを挟んでみたが、うまくいかなくて悲しい、という状態で終わった。その後、対策を考えなおした。

  • 室内機の電源ラインにもっと大きなコアを巻いてみる
  • 室内機と室外機とを繋ぐ線にCMFを入れる

室内機の電源ラインにもっと大きなコアを巻いてみる

ハムフェアにて内径19mm外形40mmの大型のコアを購入したので、これを5ターンほどエアコンの電源ラインに巻いてみた。

これは効果が抜群だった。(コアは内側を1回通る=1ターンなので、この画像で5ターン)

対策前

対策後:

コアを閉じた瞬間から減りはじめて感動的な体験をする

室内機と室外機とを繋ぐ線にCMFを入れる

かなり狭い領域かつパッツンパッツンなので、あまりコアを入れる余地がない。

とりあえず1ターン入れてみたが、あまり効果が見られなかった。複数個入れれば違うのかもしれないけど、入れるスペースがなかったためひとまず諦めた。

基本的に巻数の2乗に比例してインピーダンスが上昇するので、複数回巻けないというのはかなり厳しい。5ターンのコア1つと同じインピーダンスを1ターンのコアを複数で解決しようとすると単純に25個必要になる。

今回の教訓

1回小さいコア入れたぐらいで諦めず、複数回巻けるコアでも試してみる。

あと根本的にコアのデータシートから「このぐらいのインピーダンスが主流」っていうのを感じとっておいて、巻数でどれぐらいインピーダンスが上がるか、目安程度でも感覚的に想像できるようになると便利そう。最近得た知見としては

  • ローバンドほど対策が厳しい
    • 元々低い周波数だとコアのインピーダンスが低いので、かなり巻きまくる必要がある
    • 例えば7MHzで20Ωのインピーダンスのコアだと、1つのコアに13回巻かないと目安となる3kΩにならない。
  • 巻きすぎるとハイバンドでの特性が悪化する

村田製作所がコアの選定方法というページを公開していて便利

  1. トップ
  2. tech
  3. エアコンからアマチュア無線に混入するノイズ
  1. トップ
  2. ham
  3. エアコンからアマチュア無線に混入するノイズ

移動用の小さいパドルが欲しいなと思っていた。あまり使わないと思うので最低限キーイングができればいいかな、というぐらいの温度感だけど、割と高価なものが多い (最低でも10k) ので買ってなかった。最悪PCキーイングだけでなんとかするというのでもいいけど、割と考えが保守的なのでキーなしで運用とかビクビクしてしまう。

ハムフェアで何かいい感じのを自作している人がいたら買ってみようと思っていたが、いろいろ悩んだ結果「どこでもパドル MINI」というのを買ってみた。Wood のキットバージョンで3000円だった。

大変可愛いらしい見た目で、メインの機構はマイクロスイッチになっており、これは板バネなので細かい調整はできないが、使ってみると思ったよりかなり気持ちいい。

キットで買ったのだけれど、無鉛ハンダまでついていて親切感がだいぶ高かった (露出して手が触れやすいところにハンダ付けする必要があるための配慮のようでかなり嬉しい)

土台は買わなかったので、手元にあった金具でKX3の本体ローレット部分に固定してみた。結構いい。けど、振動でローレットとかがゆるむので、もうちょっといい形で固定したい。

縞黒檀を選んだけどかなり可愛い。自分は渋いのが好きらしい。

  1. トップ
  2. tech
  3. 移動用パドル
  1. トップ
  2. ham
  3. 移動用パドル

今までエアコンの室外機や給湯器から1mも離れていないところにアンテナを建てていたので、一念発起して場所を少し移動した。といっても、ベランダが大変狭いので、最大 2.5m 程度しか離せない。

が、やってみたらエアコンや、室内の電子機器から入るノイズはだいぶ減ってくれた。あとなぜか7MHz帯の全体的なノイズがS1からS2(2分の1から4分の1) 減ってだいぶ聞きやすくなった

before:


after (FFT後にソフトウェアでゲインの補正を入れてあるので上記とちょっと違うけど):

アンテナの位置を変えるというのは、すなわち調整しなおしなので結構なダルさがある。マルチバンドモービルホイップなため

  • 全バンドでSWRがそれなりに下がるようにカウンターポイズを試行錯誤する
  • 全バンドで共振点が狙ったところになるようにエレメント長を調整する

だいたい2時間ぐらいでとりあえずいい感じにはなった。カウンターポイズ自体もエアコンの室外機になるべく近づけないようにした。

しかし一方で、18MHz 帯のノイズが増えて厳しい感じになった。どこから入ってるのか検討もつかないけど、自分の範囲外の環境雑音っぽくてかなり厳しい。

(下の画像で横に線が連続で入ってるのはOTHレーダーかな?だいぶうざいですね)

  1. トップ
  2. tech
  3. アンテナ位置の変更
  1. トップ
  2. ham
  3. アンテナ位置の変更

三菱レイヨン・クリンスイ 蛇口直結型浄水器 クリンスイ モノ MD101 MD101-NC -

4.0 / 5.0

これ買った。クリンスイのボトルタイプを使ってたけど、めんどくさいので蛇口取り付け型を買ってみた。

ネットでいろいろ調べてみたけど、うちの水栓に対応しているか?が最後までわからなくて、かなり不親切な感じ。だいたいどのメーカーも同じなのかもしれないけど、水栓に詳しくないのでよくわからない。結局附属の部品だけでちゃんと取り付けられた。

味は明らかに違うので水飲む頻度が増える。蛇口直で即浄水された水が出るのは便利。

ちょっと時間に対して話したいことが多すぎて、各分野での掘り下げが足りなかったとは思いますが、一通り下のほうまで盛りこみました。

電子工学について:自分は学校で電子工学を体系的に学んだことはありません。

YAPC 会期中はそこらへん歩いてます。

Raspberry Pi Model B+ (Plus) -

3.0 / 5.0


アセンブリ関係

電子工作まわり

  1. トップ
  2. tech
  3. YAPC::Asia で「ウェブエンジニアのローレベルプログラミング」という発表をしました