これを寝室とリビングの1つずつ買った。黒いブタバージョンで可愛い。電源ケーブルが「しっぽ」になっているのも地味にいいデザインだなあと思う。昔ながらの陶器のブタの蚊取り線香って僕は現実で見たことがないけど、それでもブタの形が「蚊取り線香」をイメージさせるというのは、なんかおもしろい。
アースノーマットって、動いている気配が一切ないのでちょっと不安だけど一応効いてるみたい。
これを寝室とリビングの1つずつ買った。黒いブタバージョンで可愛い。電源ケーブルが「しっぽ」になっているのも地味にいいデザインだなあと思う。昔ながらの陶器のブタの蚊取り線香って僕は現実で見たことがないけど、それでもブタの形が「蚊取り線香」をイメージさせるというのは、なんかおもしろい。
アースノーマットって、動いている気配が一切ないのでちょっと不安だけど一応効いてるみたい。
なんかいまいちよくわからない (以下は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 とかだとダメ。gl.RGBA は OpenGL ネイティブがこの型式だかららしいけど、gl.BGRA でもいけるのがよくわからない……
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)
}
} JSON だけやりとりするなら必要ないけどこのようにも書ける
をつかう。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 はシリアル通信経由で直接 CW をエンコードして送信できる。つまりいわゆる専用の CW USB インターフェイスが必要がない。(RTTY も KX3 側でエンコードしてくれるので、これによって送信できる)
使うのは KY コマンドで
KY text;
の型式で送る。KY に続く空白を W にした場合、このコマンドで指定した文字列の送信が終わるまで、後続のコマンドの実行が保留される。(一旦キーの速度を変える場合に便利)
コマンドのリファレンスには「@ を送ると即時送信停止になるよ」というようなことが書いてあるが、これは K2 というリグの話で、KX3 ではあてはまらない。
KX3 では @ は常に @ のモールス符号が送出されるようになっていて、即時停止には RX コマンドを使う。RX コマンドは即時に送信を止めて受信モードに戻る (送信中符号の送信完了も待たない)
KX3 側のバッファはあまりないみたいだが、TB コマンドで今何バイトバッファに入っているかを知ることができる。ただ、9文字以上だと常に9になってしまうので、正確にはわからない。
この機能を使って PC キーイングを作る場合以下のようになりそう
KX3 側に保持されているバッファの内容を取得することはできず、残り送信文字数だけしかわからない。なので同期が正確にできないと厳しい。この機能は送信中にコマンド実行する必要があるので RFI が比較的でやすく信用できるかというと微妙な気がする。
とりあえず既に USB CW インターフェイスを作ったときの UI があって、WebSocket の互換サーバーさえ書ければ動かせるので、試してみる。
Mac はかっこいいんだけど、無線機とかのボタンとツマミがいっぱいあるかっこよさとは方向が違うんだよな。
スタイリッシュであることは翻っておもねり的ダサさも感じさせるから、そうじゃないボタンとツマミにまみれたナードデバイスというのは安定のかっこよさを持ってる。
15時ぐらいから悪寒がしたが MTG が連続で入っていたのでとりあえず出てから帰った。一晩で治るかなと期待したがそんなことなく1日休み
最近多いなと思ったので日記検索した
できたものはこんな感じのです。
KX3 にはオーディオ帯域までダウンコンバージョンされた信号が直接でる出力端子があり、一般的なステレオ音声入力を持つコンピュータを使ったソフトウェア解析により広帯域の無線信号を復調できる。
なのでこの信号を使って MacBook 上で FFT して広域の時系列データをウォーターフォール表示し、信号を高速で見つけられるようにしたい。
具体的には Windows においては OmniRig 及び HDSDR によって実現できることを Mac で行いたい。
OmniRig と HDSDR は Mac 上に VM を立てて Windows を動かせば当然動くが、残念ながらパフォーマンスがあまりでない。特に CPU 使用率が非常に高くなってエコではない。
wine によるエミュレーションを行ってもある程度動かすことができるが、自分の環境では KX3 とのシリアル通信に難があった (書きこみはできるが読みこみができない) また、この方法も CPU リソースを大量に使う。
このため、Mac OS X 上でネイティブに動く似たようなソフトウェアが必要だと感じた。
コンピュータ側で復調したりする機能は今回特に必要性を感じなかったので大変シンプルな要件
また、できれば KX3 側の局発を変えても、ウォーターフォール表示の履歴をスムーズに繋げたいと思った (既存の SDR ソフトだと必ず局発を中心にウォーターフォールが表示されてしまうため、時系列データがずれて表示されてしまう)
今回実装言語として go を採用した。以下の理由がある
音声入力まわりは portaudio、グラフィックス表示まわりは OpenGL を使って実装した。
入力の I/Q 信号は局発の位相を90度ずらしてコンバージョンした直交信号になっており、これを複素数として FFT すると、正の周波数と負の周波数を一発で解析できる。今まで FFT しても実数しか扱ったことがなく負の周波数部分は捨てたことしかなかったが初めてちゃんと使った。
基本、表示側はこれをうまいこと表示しているだけでおわってる。バッファは container/ring の ring.Ring でリングバッファとしていて、解析済みのビットマップを1行そのまま保持している。
goserial というライブラリがあるので基本これを使ってやるだけだけど、いろいろとハマる。
go に慣れていなくて、make(chan T) し忘れたせいで一晩ハマったりもした (なぜか1度だけ chan 通信が動くという謎挙動になった)。
当初の目的はひとまず果たすことができた。
40m (7MHz) 帯を見ながら実験してたけど、このバンドは終始賑やかというのが可視化されて面白かった。逆に、他のバンドはいつ見てもうちのロケーションでは殆ど聞こえないということもわかった。悲しい事実も可視化された。
あと気になっているのは、0Hz付近に常に高いスペクトルがでてしまうことだけど、よくわかってない。3000円ぐらいの安いサンプリングデバイスを使っているせいかもしれないけど、良さげなのは結構なお値段なのできつい。
これが欲しいです。
"KXPD3 Keyer Paddle" は普通の KEY 端子とは別に、下部で4ピンで出ているのを使っている。マニュアルにも回路図にもピン配置が書いていないので調べた。(KXPD3 は持ってない)
ピン番号は回路図に倣った。KX3SchematicDiagramDec2012.pdf の9ページ
3V3 は何のためについているかわからないけど、330Ωがついているので10mAしか流れない。実測したけど9mAしか流れない。
GND はケースに導通しているのと共通
と思いはじめ、いろいろ調べてみた。移動といっても車も自転車もないので、バッテリーやアンテナなども含め徒歩や公共交通機関で運搬可能な範囲でなければならない。
国内だと FT-817ND というリグがこの用途ではスタンダードなようでかなりたくさんの人が使っている。FT-817ND は2001年発売とかなので、おそろしく長く売ってる無線機となっていて、国内には競合製品がない。
ほかにも候補があるか?と思ってさらに調べてみると、アメリカ Elecraft 社の KX3 というのが良さそうだということがわかった。こちらは2012年?ごろに発売された比較的新しい無線機で、FT-817ND と同じぐらい小さい。
結論からいうと表題の通り KX3 を買ったが、以下のポイントで決めた
問題は主にコスト面で、FT-817ND がフィルタ込みで9万円ぐらいで買えるのに対し、KX3 は必要なオプションを入れていくと1.5倍ぐらいになる。また、技適を取得していない無線機なので、局免をとるのに結構ハードルがある。
特に何も考えず Elecraft に直で注文した。
国外にあるうちは USPS のほうが細かくステータスがアップデートされるけど、国内に入った瞬間からJPのほうが早く更新されるようになるみたい。2時間ぐらい遅れてUSPSにも反映される。
モジュールキットと完成品とがあるんだけど、せっかくなのでモジュールキットにした。地味にこういうの苦手なのでちょっと心配なんだけど、Elecraft の About ページ 見てたらキットにしなければならない気がしてきたのでそうした。
ケーブルセットは案外 L 型のコネクタが手に入りにくいので買っておいた。ルーフィングフィルターは自分の用途だとたぶん必要ないけど、CW メインでやるという意気込みをもって追加した (あとから追加すると調整が面倒)。アンテナチューナーはワイヤーアンテナでもいける感じのやつなので、最悪長いケーブルさえあれば出れるように追加した。
なかなかかかる。無線機に関税はかからないけど、消費税は税関でかかる。
注文しちゃったあとで調べたら国内代理店だと、ちょうど代理店の手数料分1万ちょいだけ浮く感じだった。直販だと届くまでに日数がかかるのと、ちゃんと届くのが不安とかがあるので、代理店で買ってもいいレベルの差ではあるかもなあという印象。
思いのほか小さい箱で届く。
検品からはじめて、最初に動かすまで4時間ぐらいかかってしまった。めちゃくちゃ難しいみたいなことはないんだけど、小さいだけあって中身が狭いので、ビス締めるのが微妙にむずかったり、ケーブルの接続がしにくいとかがあった。とりあえずちゃんと動いたのでよかった。
ビスの長さが一部 (スピーカーまわり) で違ったんだけどなんとかなったっぽい。アメリカンな感じ…
ファームウェアはなぜか最初から最新 (2014-07-11) の beta 版 (production 版と別れてるんだけど) が書きこまれていたのでアップデートはしていない。
よくあるツールなんだけど、なかなか希望に叶うものというと見つけにくく、どうせなら自分で書いたらいいかと思ったので書いてみた。やってみたら割とすぐ書けた。
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
電源のテストを行いたいときは、適当な抵抗を繋いだりするわけだが、特定の抵抗値を狙ってつくるのはめんどうくさく、また許容損失が大きいものはつくりにくい。
そこでパワーバイポーラトランジスタやパワーFETを使って可変抵抗にする、というのが電子負荷らしい。
電子負荷には定抵抗モード・定電流モード・定電力モードなどいろいろあるが、今回は面倒なので定電流モードのだけを考えてある。
とりあえずこんな感じになった。
メインのパワーFETは2SK1122に
許容損失は温度上昇に伴なって下がっていくが、このFETの場合 60℃ぐらいでは70W、90℃までいくと50Wになる。
放熱が必須なのでとりあえず安いCPUクーラーを買った。1000円もしないが PWM 制御のファン付きでお得
まず単体の 2SK1122 のゲートに可変抵抗で分圧した電圧を加えつつ、放熱器に貼りつけて 12V 2A 程度まであげつつ流してみた。
指で触れられないぐらい熱くなるということがまずわかったので、ちゃんと温度を測りながらにするため、直読温度センサーも一緒に貼りつけて温度を実測しながらに切り替えた。
FET単体でも定電流素子として使われることがある通り、これでもある程度は安定して流れてくれる。ただ、FET が定電流なのは温度が変わらないことや、負荷電圧が変化しないことが条件なので、温度上昇に伴なって電流量は増えていってしまい、電流量が増えるとさらに発熱するという、ある種の暴走状態になる。この実験では放熱器をつけて、電源側で最大2Aに制限しているので、65℃ぐらいで安定する感じだった。
電流制限を安定させるため、オペアンプのフィードバックをつけた。オペアンプは単電源でつかえて安い LM385 にした。動作が遅いので発振もしにくそう。
オペアンプの入力の +/- は常に同じ電位になるように調整される (バーチャルショート/イマジナリショート)
+ に入れた電圧と、抵抗上部の電圧が等しくなるようにオペアンプの出力が自動でいい感じに変化するという挙動になるため、+ に入れた電圧と抵抗の比 (I=V/R) にのみ依存する形で定電流動作をするようになる。いろいろ別に抵抗がついているのは動作安定のため (FETの入力の抵抗はオペアンプの出力電流を制限するため・帰還の抵抗は発振防止)。
入力側の分圧抵抗によってレンジを決めている。入力する最大電圧を設定することで最大電流を決められる。
放熱器に素子をもっと押しつけるといいみたいな話を聞いたので、FETまわりだけケースに取り付けることにした。CPU クーラーなのでプッシュピンがついており、固定にはこれを使える。だいたい 75mm の幅でM4の穴をあけると丁度いいみたい。
ただ、これだけだとFETがケースにちゃんと接触せず、圧力がかからないので、適当なナットを貼りつけて厚さを増やしている。
この状態で 2A 12V かけると 55℃程度で安定する。13.8V 5A かけると90℃ぐらいで安定する (69W 90℃なので許容損失はオーバーだけど)
コントローラはAVRで、以下のような機能を実装してある
表示は4桁の7セグを1セグごとにダイナミック点灯させている。1桁ずつ表示するのが普通っぽい?けど、部品を増やしたくなかったので、同時に電流が流れすぎないようにした。
やばそうなとき強制停止する機能をつけたかったので、オペアンプの入力用の電圧はAVRのポートから出している。ただ、あんまりこれは電圧が安定していないので、シャントレギュレータで基準電圧をつくってからボリュームで分圧してる。上側のボリュームは半固定で、最大値を調整するためにつけてる。実際操作するのは下側のボリュームになってる。
いまいちイメージがしにくくて理解したとはいいきれてない部分の覚書
平衡経路は差動信号、すなわち位相が反転した信号を2線に乗せ、接地せずに伝送する。
対して不平衡経路はシングルエンド、すなわり片方を接地させて信号を伝送する。
信号伝送の形式が違うので、当然直接接続してはいけない。直接接続した場合、不平衡側は GND を基準としているので、GND の変動はすなわち信号線にもGNDが変動しただけの変動が発生するということであり。これはコモンモードのノイズということになる。
バランによって平衡・不平衡を接続することができる。
電圧バラン(トランス)の場合、差動信号の入力は出力からすると単に信号がおおきくなったようにみえる。GND は絶縁されており、コモンモードは発生しない。
Uマッチというバランの場合、片方の伝送路を1/2λ遅延させることによって位相を反転させて逆相を得て平衡信号を作りだす
電流バランの場合、GND変動によって発生するコモンモード電流を遮断する形で機能する。性質上完全に遮断しにくいが、ロスも少ない。