ebay で800円ぐらいで買ったものです。

この手のモジュールにはAD9851(源クロック6倍周波数逓倍器付き)のものとAD9850のものがあり、さらに基板のタイプが2つあります。AD9850/AD9851は制御方法も含めてほとんど互換なので、源クロックとチップだけ違うものが出回っているみたいです。

試してみたのはおそらく古いタイプのもので、ブレッドボードには直接挿すことができないので、ちょっと面倒くさいタイプです。

周波数の設定・パワーダウンモードへの移行・設定位相(2台以上のDDSでsin/cos作る場合に使う)を40bitレジスタにまとめて突っ込む形になっています。

この40bitレジスタはパラレルモードとシリアルモードで設定でき、ググると大抵の例でシリアルモードを使ってるみたいです。

ただ、リセット直後はパラレルモードで起動するので、シリアルモードに変えてやる必要があります。といっても、D0=1 D1=1 D2=0 にした状態で、W_CLK パルスを送り、すぐ FQ_UD パルスを送るとシリアルモードに入るということになっています。

このモジュールの場合、ジャンパでD0=1 D1=1 D2=0 を設定できるようになっており、デフォルトでジャンパしてあるので、W_CLK と FQ_UD パルスをマイコンから送るだけで初期化できます。

コード

ライブラリみたいにしてあるのもありましたが、結局自分で書きました。

入手したモジュールは125MHzが源クロックでした。

#include <Arduino.h>
#include <Wire.h>

template <uint32_t CLKIN>
class AD9850 {
	static constexpr double PHASE_FACTOR = 0x100000000 / (double)CLKIN;

	const uint16_t PIN_DATA;
	const uint16_t PIN_FQ_UD;
	const uint16_t PIN_W_CLK;
	const uint16_t PIN_RESET;

	void serial_write(uint32_t freq, uint8_t phase, bool powerdown) {
		// freq (delta phase)
		for (int i = 0; i < 32; i++) {
			digitalWrite(PIN_DATA, (freq>>i) & 1);
			digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
			digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);
		}

		// control bits
		digitalWrite(PIN_DATA, LOW);
		digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);
		digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);

		// powerdown
		digitalWrite(PIN_DATA, powerdown ? HIGH : LOW);
		digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);

		// phase
		for (int i = 0; i < 5; i++) {
			digitalWrite(PIN_DATA, (phase>>i) & 1);
			digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
			digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);
		}

		digitalWrite(PIN_FQ_UD, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_FQ_UD, LOW); delayMicroseconds(4);
	}

public:
	AD9850(
			uint16_t data,
			uint16_t fq_ud,
			uint16_t w_clk,
			uint16_t reset
		) :
			PIN_DATA(data),
			PIN_FQ_UD(fq_ud),
			PIN_W_CLK(w_clk),
			PIN_RESET(reset)
	{
		pinMode(PIN_DATA, OUTPUT);
		pinMode(PIN_FQ_UD, OUTPUT);
		pinMode(PIN_W_CLK, OUTPUT);
		pinMode(PIN_RESET, OUTPUT);
	}
	
	/**
	 * W0 ... W31  -> Freq (LSB first)
	 * W32, W33    -> Control (for factory test)
	 * W34         -> Power-Down
	 * W35 ... W39 -> Phase (LSB first)
	 */

	void reset() {
		// ensure low
		digitalWrite(PIN_DATA, LOW);
		digitalWrite(PIN_FQ_UD, LOW);
		digitalWrite(PIN_W_CLK, LOW);

		// reset
		digitalWrite(PIN_RESET, HIGH); delay(1);
		digitalWrite(PIN_RESET, LOW); delay(1);

		// reset to serial mode
		// Pins of D0, D1 = HIGH, D2 = LOW for serial mode
		digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);

		digitalWrite(PIN_FQ_UD, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_FQ_UD, LOW); delayMicroseconds(4);
	}

	void set_frequency(uint32_t frequency) {
		set_frequency(frequency, 0);
	}

	void set_frequency(uint32_t frequency, uint8_t phase) {
		uint32_t deltaPhase = PHASE_FACTOR * frequency;
		serial_write(deltaPhase, phase, 0);
	}

	void powerdown() {
		serial_write(0, 0, 1);
	}
};

AD9850<125000000> ad9850(9, 10, 11, 12);

void setup() {
	Serial.begin(9600);

	ad9850.reset();
	ad9850.set_frequency(10e6);
}

void loop() {
}

出力

商品説明だと40MHz まで出せると書いてありました。たしかに出せることは出せるようです。

このモジュールの出力には7次のローパスフィルタがついています。これはAD9851のデータシート通りの定数のフィルタのようで、70MHzぐらいにカットオフ周波数ががあるみたいです。

なおローパスフィルタの後の出力インピーダンスは200Ωになっているみたいです。

AD9850自体の出力は電流出力で、12ピンについている抵抗でフルスケールの出力電流が決まることになっています。このモジュールでは3.9kΩが実装されており、10mA フルスケールの出力に設定されています。200Ωで10mW(10dBm)。実際使う場合バッファして50Ω出力とする必要はありそうです。

片方だけにローパスフィルタが入っており、もう片方の出力は200Ωで電圧変換されて直接ピンヘッダに出ています。ということで差動出力には使えません。





  1. トップ
  2. tech
  3. AD9850 DDS モジュール

AD9850 DDS モジュール に続き AD9851 で、別基板バージョンのものです。

ジャンパとかが一切ない簡略版?なのか進化版?でしょうか。

D0, D1 は 10kΩでプルアップされているので、これらは自分でプルアップする必要はありません。D2 を GND に接続するだけでシリアルモードに入れる実装になっています。

コード

#include <Arduino.h>
#include <Wire.h>

template <uint32_t CLKIN, bool MULTIPLIER>
class AD9851 {
	static constexpr double PHASE_FACTOR = 0x100000000 / (double)(CLKIN * (MULTIPLIER ? 6 : 1));

	const uint16_t PIN_DATA;
	const uint16_t PIN_FQ_UD;
	const uint16_t PIN_W_CLK;
	const uint16_t PIN_RESET;

	void serial_write(uint32_t freq, uint8_t phase, bool powerdown) {
		// freq (delta phase)
		for (int i = 0; i < 32; i++) {
			digitalWrite(PIN_DATA, (freq>>i) & 1);
			digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
			digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);
		}

		// control bits
		digitalWrite(PIN_DATA, MULTIPLIER ? HIGH : LOW);
		digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);
		digitalWrite(PIN_DATA, LOW);
		digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);

		// powerdown
		digitalWrite(PIN_DATA, powerdown ? HIGH : LOW);
		digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);

		// phase
		for (int i = 0; i < 5; i++) {
			digitalWrite(PIN_DATA, (phase>>i) & 1);
			digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
			digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);
		}

		digitalWrite(PIN_FQ_UD, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_FQ_UD, LOW); delayMicroseconds(4);
	}

public:
	AD9851(
			uint16_t data,
			uint16_t fq_ud,
			uint16_t w_clk,
			uint16_t reset
		) :
			PIN_DATA(data),
			PIN_FQ_UD(fq_ud),
			PIN_W_CLK(w_clk),
			PIN_RESET(reset)
	{
		pinMode(PIN_DATA, OUTPUT);
		pinMode(PIN_FQ_UD, OUTPUT);
		pinMode(PIN_W_CLK, OUTPUT);
		pinMode(PIN_RESET, OUTPUT);
	}
	
	/**
	 * W0 ... W31  -> Freq (LSB first)
	 * W32, W33    -> Control (for factory test)
	 * W34         -> Power-Down
	 * W35 ... W39 -> Phase (LSB first)
	 */

	void reset() {
		// ensure low
		digitalWrite(PIN_DATA, LOW);
		digitalWrite(PIN_FQ_UD, LOW);
		digitalWrite(PIN_W_CLK, LOW);

		// reset
		digitalWrite(PIN_RESET, HIGH); delay(1);
		digitalWrite(PIN_RESET, LOW); delay(1);

		// reset to serial mode
		// Pins of D0, D1 = HIGH, D2 = LOW for serial mode
		digitalWrite(PIN_W_CLK, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_W_CLK, LOW); delayMicroseconds(4);

		digitalWrite(PIN_FQ_UD, HIGH); delayMicroseconds(4);
		digitalWrite(PIN_FQ_UD, LOW); delayMicroseconds(4);
	}

	void set_frequency(uint32_t frequency) {
		set_frequency(frequency, 0);
	}

	void set_frequency(uint32_t frequency, uint8_t phase) {
		uint32_t deltaPhase = PHASE_FACTOR * frequency;
		serial_write(deltaPhase, phase, 0);
	}

	void powerdown() {
		serial_write(0, 0, 1);
	}
};

AD9851<30000000, true> ad9851(9, 10, 11, 12);

void setup() {
	Serial.begin(9600);

	ad9851.reset();
	ad9851.set_frequency(10e6);
}

void loop() {
}

AD9851 とほぼ同じですが、6倍の周波数逓倍器がの有効無効化のビットがあるので、その部分だけ実装を変えてあります。

出力

購入したモジュールは 30MHz の源発振のものなので、逓倍後は 180MHz になります。

40MHz ぐらいから出力が低くなります。

ZOUT2 (フィルタあり)



逓倍している分スプリアスが多いようです。


ZOUT1 (フィルタなし)


  1. トップ
  2. tech
  3. AD9851 DDS モジュール

ebay で見つけて買ったやつシリーズです。表題のようなものを買ったので動かしました。

https://github.com/adafruit/Adafruit_LED_Backpack が割とよくできていて、これ使えばすぐに動かせました。

#include <Arduino.h>
#include <Wire.h>
// https://github.com/adafruit/Adafruit_LED_Backpack
#include "Adafruit_LEDBackpack.h"

// Adafruit_LEDBackpack matrix = Adafruit_LEDBackpack();
Adafruit_8x8matrix matrix = Adafruit_8x8matrix();


void setup() {
	Serial.begin(9600);
	Serial.println("begin");
	matrix.begin(0x70);
	matrix.setBrightness(10);
	matrix.setTextSize(1);
	matrix.setTextWrap(false);
	matrix.setRotation(1);
}


void loop() {
	char* message = const_cast<char*>("Hello, World!\n");

	int16_t x, y;
	uint16_t w, h;
	// getTextBounds は \n で終わってないと width を正しく計算しない。
	matrix.getTextBounds(message, 0, 0, &x, &y, &w, &h);
	Serial.println("getTextBounds: ");
	Serial.print("  x = "); Serial.println(x);
	Serial.print("  y = "); Serial.println(y);
	Serial.print("  w = "); Serial.println(w);
	Serial.print("  h = "); Serial.println(h);
	Serial.println("");

	for (int16_t x = 8; x >= -(int16_t)w; x--) {
		matrix.clear();
		matrix.setCursor(x, 0);
		matrix.print(message);
		matrix.writeDisplay();
		delay(50);
	}
}
  1. トップ
  2. tech
  3. I2C 8*8 LED dot Matrix module HT16K33

BMP180 搭載のモジュールを ebay で買ってみたので試しました。約$2。どの気圧計にしろ温度計が必要で内部補正には使われていたりしますがだいたい内部用で外から値がとれません。このモジュールは温度もI2C経由で測れて一石二鳥モジュールです。

こんな感じのモジュールで、BMP180 以外に実装があります。これは 3.3V レギュレータで、5V 供給しても大丈夫なようになっています。(I2C のロジックレベル変換は簡易的ですが)。なので 5V の Arduino でも使えます。

BMP085 というものと互換性があるみたいで (BMP085はディスコン) それ用のライブラリがそのまま使えます。

#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_BMP085.h>

// https://github.com/adafruit/Adafruit-BMP085-Library
Adafruit_BMP085 bmp;

void setup() {
	Serial.begin(9600);
	int ok = bmp.begin();
	if (!ok) {
		Serial.println("bmp.begin() failed.");
		for (;;);
	}
}

void loop() {
	Serial.print("Temperature = ");
	Serial.print(bmp.readTemperature());
	Serial.println(" *C");

	Serial.print("Pressure = ");
	Serial.print(bmp.readPressure() / 100.0);
	Serial.println(" hPa");

	Serial.println();
	delay(1000);
}
Temperature = 19.40 *C
Pressure = 1011.48 Pa

Temperature = 19.40 *C
Pressure = 1011.47 Pa
  1. トップ
  2. tech
  3. BMP180 I2C 気圧・温度計センサー

ebay で買ったシリーズです。$3 ぐらいで買いました。以下らへんのモジュールとよく似たものです。

MQ-135 のほかすこし実装 (LM393など) があります。LM393 はコンパレータで、基板上の半固定で設定した閾値でデジタル出力できるようになっています。アナログ出力は MQ-135 直結です。

MQ-135

空気の汚染度に感度があるセンサーで、ベンジン・アルコール・煙などに反応しやすいと書いてあります。CO2 にもある程度反応するみたいで、ググると poor man's CO2 センサーとしても一定の人気があるみたいです。

自分としては、換気扇ごしに外からタバコの煙が自宅に入ってくることがあってほとほと不愉快なので、どんな時間帯にそういう状況になるかをつきとめたいというモチベーションがありますが、そういう用途に使えるかはわかりません。というかテストのためにタバコを買うのも絶対に嫌なのでテスト不可能です。

仕様

MQ-135 自体がデータシートでプリヒートを24時間以上求めています。常時起動してないとダメですね。とはいえ1時間ぐらいで値はほぼ安定しているようには見えます。

MQ-135のデータシート的には10kΩ〜47kΩの負荷抵抗を想定しているみたいですが、このモジュールは 102 (1kΩ) の抵抗が負荷抵抗になっているように見えますので、普通に使うより感度が低くなりそうです。

ググると頑張って ppm 単位の絶対値出力を出そうという試みがあるのですが、素人がキャリブレーションしようがない (他の正確な絶対値センサーが必要) ので、基本的には相対的な値を範囲を一定期間モニタリングしたうえで値を把握して使うほかない気がします。

実測出力

  • 200mV / 数時間通電後の無人の部屋
  • 700mV / アルコールティッシュを近付けたとき
  • 800mV / サカムケアを近付けたとき (イソプロパノールなど)
  • 1660mV / プラモデル用接着剤を近付けたとき (アセトンなど)

意外と反応が良くて、臭いがあるとすぐ変化が出力されます。

  1. トップ
  2. tech
  3. MQ-135 Sensor Air Quality Sensor Hazardous Gas Detection LM393 MQ135 gas sensor

神道の文献は、たとえ最古の古事記(712年)であっても、仏教伝来(遅くとも6世紀半ば)後に書かれたものであるので、そもそも「仏教に影響されていない神道」というものを記録している文献はない。すなわち仏教抜きで神道を考えるというというのはそもそも現代では無理。

「復古神道」は名前に反して江戸時代の新興宗教であって、儒教・仏教色を排すという意味ではかえってそれらの影響をうけている。

C++ はながいこと食わず嫌いだった。とにかく「難しい」というイメージだけ先行していた。

しかし、あくまで better C として使う限りでは難しくないし、可読性が上がるので、 C++ を使わない手はないという気持ちになった。

これまでの漠然とした C++ への不安

書いたコードと出てくるバイナリ

C を書いていると、だいたいは書いたコードがそのままバイナリに翻訳されている感があり、安心感がある。struct はメモリ上の配置そのままだし、関数だって単に処理のまとまりに名前がついてて、呼び出しもとに自動的に戻ってくるラベルなだけだ。

C++ になるとオブジェクト指向に概念が入ってきて、これが実際どのようにコンパイルされるかに不安が出てくる。クラスはメモリ上でどう表現されているのか、メソッドディスパッチはどのように行われているか?

もちろん知っている人にとっては簡単な話で

  • class は単に struct である
    • フィールド定義はそのままメモリ上の表現となる
    • メソッド定義は関数になる
    • 実際、C++ で class と書かれた定義を struct に置き換えても問題なくコンパイルされる (フィールドのスコープがデフォルトで class は private で struct が public である違いだけ)
  • メソッドディスパッチは基本的に静的である
    • レシーバによって動的にディスパッチされる関数は virtual 関数と呼ばれる
    • コンパイラがテーブルを作って動的に呼びだし関数を変えている

ということで、virtual を使わない限りでは、C++ の class は C で struct 定義と、その第一引数にその sturct ポインタをとる関数郡でしかなく、これは C でよくやるオブジェクト指向のプリミティブな実装とよく似ている。

このようなCのコードは

#include <stdio.h>

typedef struct {
	unsigned counter;
} my_counter;

void my_counter_init(my_counter* this) {
	this->counter = 0;
}

void my_counter_incr(my_counter* this) {
	this->counter++;
}

unsigned my_counter_get_count(my_counter* this) {
	return this->counter;
}

int main() {
	my_counter counter;
	my_counter_init(&counter);

	printf("%d\n", my_counter_get_count(&counter));
	my_counter_incr(&counter);

	printf("%d\n", my_counter_get_count(&counter));
}

このようなC++のコードとほとんど同じバイナリが出力される

#include <cstdio>

class my_counter {
	unsigned counter;
public:
	my_counter(); // void init();
	void incr();
	unsigned get_count();
};

my_counter::my_counter() {
	this->counter = 0;
}

void my_counter::incr() {
	this->counter++;
}

unsigned my_counter::get_count() {
	return this->counter;
}

int main() {
	my_counter counter;

	printf("%d\n", counter.get_count());
	counter.incr();

	printf("%d\n", counter.get_count());
}

テンプレート多用するとバイナリサイズ増えるんじゃないの?

テンプレートは、複数の型に対する処理を1回でまとめて書けるという仕組みなので、原理的には、扱う型が増えるほど、出力バイナリに関数本体が増えていくことになる。

しかし一方で、テンプレートをコンパイル時計算のために使うような場合は、コンパイル時に解決されるコードがほとんどになり、出力バイナリは書いたコードの見た目の多さに反してかなり少なくなることもある

ということで、テンプレートが使われているからといってバイナリサイズが肥大化するというわけではない。

最適化はどこまで信用できるか?

この書きかたで本当に最適化されたコードに出てくるの? という不安がある。これは難しくて、コンパイラが優秀でも、当然プログラマがちゃんと const を付けてコンパイラに意図を伝えないと、完全に最適化されたコードにはならない (インライン化されないとか)。

C にはない概念がある分、const の付けかたが複雑で、ベターCとして使おうと思うと一番ハマる。

一方で、テンプレートメタプログラミングでコンパイル時に計算することができるので、コンパイラが判断できないような高度な最適化を自力でやることができる。

なぜ組込みでこそなのか?

組込みのコードはどうしてもマジックナンバーが多くなりやすく、C で書くとマクロだらけになる。Cのマクロは文字列展開なので型がなく、当然マジックナンバーにも型を付与できない。組込みは実行時デバッグのコストが高いので、コンパイル時に見つけることができるエラーは全てコンパイル時に見付けたいが、Cではそこまでのことができない。

浮動小数点まわりについてはMCUでは非常に重い処理になるので、できるだけ事前計算した係数をつかってMCU上では整数演算にしてしまうなどの最適化をしたくなる。こういうときCのマクロだけで書くのはとても厳しい。C++ であればコンパイル時に浮動小数点計算を行って定数展開できるので、あきらかに有利になる。

浮動小数点に限らず、高いレイヤーでの最適化はコンパイラは知るよしもないので手でやる必要がある。その際C++のTMPは非常に強力に使える。

  1. トップ
  2. tech
  3. 組込みでこそ C++

測定中、10MHz 付近に急激な落ちこみが観測されることがあって気になっていました。そういうものなのかなとも思ったのですが、調べてみたら同じような問題にあたっている人がいて、まぁよく考えたらそういうものではないよなという感じです。

これは簡単に再現することができます。TGとSAを同軸で直結させて、TGを有効にし、10MHz 付近を拡大することで、変な落ちこみを見ることができます。

以下の画像がそうですが、明かに1dB 落ちこみがあるのがわかると思います。

あるいは、掲示板中にもありますが 10μH と 33pF ぐらいを直列に接続し、バンドパスフィルタを構成した上で 10MHz 付近を見ると、スカートが不自然になっているので非常に目立ちます。 冒頭の画像はこの方法での再現です。直結よりもわかりやすくおかしいことがわかります。

広いスパンで見ているときや、広いダイナミックレンジで見ているときには気付きにくいかもしれませんが、フィルタのようなものを測定するときはかなり気になる挙動だと思います。

10MHz 付近はアマチュア無線でも使う帯域ですし、基準信号付近ですから余計気になります。位相雑音がどうとかよりも致命的な問題でしょう。

現時点での解決方法

ファームウェアの 1.15 が1月22日にリリースされており、これにある程度の修正が含まれています。

最新のファームウェアは http://int.rigol.com/Support/SoftDownload/3 からダウンロードできます。

Product Series → Spectrum Analyzer → DSA815 Firmware_00.01.15.01.00 というのをダウンロードします。DSA815(DSP)update.rar というやつです。

DSA815update.sys を USB メモリのルートディレクトリにコピーして、DSA815 のフロントUSBポートに接続し、Storage の画面でこのファイルを選択したあと、Sys Update を選択すると、アップデートが開始されます。数分で終わり、再起動すると最新のファームウェアで起動します。

バッファロー USB3.0メモリ バリューモデル (8GB・ブラック)RUF3-WB8G-BK - バッファロー

バッファロー

3.0 / 5.0

↑ 使ったUSBメモリです。自宅にUSBメモリがなく、SDカードリーダーとSDカードで代用しようと思いましたが、DSA815 では認識されませんでした。純粋な USB メモリじゃないとダメなようです。ググってみると、メーカー的にはできるだけ容量が少ないほうが信頼性が高いということになっているみたいです。とはいえ現時点で新品はもはら8GB未満は売ってません。これは USB 3.0 メモリですが、DSA815 は USB 3.0 に対応してないので、古くてもなんでもいいはずです (信頼性は別として)。

解決したか?


見ての通りです。直結の場合では不連続部分はわかりません。一方バンドパスフィルタで見たときはすこし不連続な部分が残っているようにみえます。

ということで、完全には解決しません。挙動的にはまるめ誤差みたいなのが発生しているような感じますが実装がどうなっているかはわかりません。

とはいえ、このように修正が入るので、現状の状態は Rigol のエンジニアは把握しており、直すつもりはあるようです。ファームウェアアップデートだけですめばいいんですが……

ファームウェアアップデートと保証

DSA815-TG には3年保証がついているのですが、ファームウェアアップデート時にどのような扱いになるか不安になりました。(そもそも国内販売版のものが国際版と完全に同一であるかの確証もない)

国内のリゴルジャパンへメールで確認をとったところ、すぐに返信していただけました。

  • 国内販売のものでもファームウェアは共通
  • ファームウェアアップグレードしても保証期間内では無償サポート

とのことで、アップグレードしても大丈夫そうです。

1.15 での他に気になった挙動

購入直後スクリーンショットをLAN経由でとれるようにしましたが 1.15 になって、TCPソケット経由でのコマンド解釈がすこし厳密になって動かなくなったので直しました。

あと、スクリーンショットをとるコマンドは最後にリモートモードを解除するコードを入れていたのですが、動かなくなりました。リモートからコマンドを実行すると必ずリモートモードになってしまって若干鬱陶しいのでなんとかしたいのですが、こちらの原因はよくわかってません。

どうやらスリープを挟まないとダメな模様

また、デフォルトで mDNS での DNS-SD の機能が無効になってみたいです。HTTP 経由でアクセスして設定しないと DNS-SD のクエリに応答しません。

備考

元のバージョンは00.01.12 でした。

アップグレード後は 00.01.15 です。


  1. トップ
  2. tech
  3. DSA815-TG の 10MHz 付近の奇妙なバグとファームウェアアップグレード

スペアナがあったらやってみたかったことの1つとしてコモンモードフィルタ(RFチョーク)の評価というのがあります。だいたいトロイダルコアを使って作っており、トロイダルコアは再現性が高いので計算通りにやれば実用性能はでるはずなのですが、ちゃんと作れているか?はやはりはかってみなければわかりません。

スペアナがなくてもハムバンドでダミーロードを使いつつ高周波電流を頑張って測るという方法で一応検証はできるのですが、面倒くさすぎるので結局グラフ化したりまでしたことはありません。面倒くささが勝りすぎます。

その点スペアナがあれば一瞬で…… という気持ちがありました (実際は適切な治具がないと正確に測るのはやはり難しいのですが……)

概要

大地経由での電流がコモンモード電流なので、フィルタのホットとコールドをショートして、TG のホットをフィルタを通してスペアナの入力にし、TG とスペアナのコールド側をショートさせれば計れそうです。要は単にRFチョークとして測定します。

なおコモンモードフィルタはだいたい-30dB ぐらいあれば十分な性能といえるらしいです。

測定方法

まずワニグチクリップをトラッキングジェネレータとスペアナ入力に直付けし、ホットコールドをそれぞれショートさせます。

この状態でTGの出力レベルを0dBmに設定し、ノーマライズします。(なおビビリなので外付けのアッテネータは入れてませんが内蔵アッテネータは10dB入れたままです) それなりに出力がないとノイズに測定結果が隠れてしまうので気をつけます。

ノーマライズされた状態でホット側をはずしてアイソレーションを確認します。-30dB 以上なければ測定の意味がないので、アイソレーションがとれてなさそうなら諦めてスパンを狭くして再度ノーマライズからやりなおします (意味がない結果が見えていると混乱するため)。

TG 側にワニグチクリップをつけたりして配線長を増やすと50MHz〜は容易にアイソレーションがとれなくなるので注意します。以下はオープンの状態のアイソレーションです。

この状態でフィルタに接続して測ります。フィルタの入力側(TG側)の線長が長いとこれまたアイソレーションがとれないので誤差の原因になりますが、排除するのが難しいです。シールドケースに入れて測るしかない気がしますが、今回は用意できてないので多少飛びこみがあります。

結果

今回計ったのはキットものの http://www.ddd-daishin.sakura.ne.jp/ddd/dcf/dcf-rf-29l3/kit-dcf-rf-29l3-ver2.htm これです。スペック的には 50MHz ぐらいまでは -30dB 余裕でとれるみたいなグラフがあります。

一応このように雑に測った感じでも 50MHz ぐらいまでは -30dB ぐらいになっているように見えます。(0〜100MHzのスパンなので真ん中が50MHzです)。とはいえ -40dB までとれている範囲はそれほどないです。

挿入損失

ついでに挿入損失も測りました。


メモ:挿入インピーダンスとdB

入出力インピーダンス50Ωとして、例えばインピーダンスZを直列挿入した場合、入力側にかかる電圧は出力電圧を、挿入インピーダンス と入力インピーダンス で分圧した形になる。

なので、入力電圧は になる。dB で表わすと

  1. トップ
  2. tech
  3. アマチュア無線用コモンモードフィルタの特性評価

RIGOL (リゴル) 1.5GHzスペクトラム・アナライザ(トラッキング・ジェネレータ機能付) 【国内正規品】,DSA815-TG - RIGOL

RIGOL

4.0 / 5.0

やっぱスペアナは欲しいよなあと思いはじめて数ヶ月、決心がついたので買いました。技術力が低いので本当に金額に見合うぶんだけの活用ができるかは微妙ですが「見えないものが見えるようになる」というメリットに抗えませんでした。

スペアナといっても現状ではいくつも選択肢があります。USB 接続のものはハードのインターフェイスがいらない分をスペックにコストをかけられるので、スペック的には良さそうです。

DSA815-TG は位相雑音(ゆらぎ/時間ドメインでのジッタ)が多いというのが大きな欠点の一つのようです。高純度の信号の測定ができないことを意味しますが、この欠点がどれぐらい今後に影響してくるのかまだわかっていません。

しかしやはり欠点はあってもスタンドアロン型のもののほうが使い勝手は良さそうです。電源入れれば使えるというのは大事に感じます。USB 接続してアプリを立ちあげてという手順は地味にだるいのです。そういうところを考えると、スペックが良くても使う頻度が少なくなってしまうのは一番損になりますから、スタンドアロンのものにしました。

この機種は数年前までは15万前後で買えたみたいですが、円安進行のためか、かなり値上りしています。悔しいところですが諦めて18万ぐらいで買いました。当然トラッキングジェネレータ付きです。

所感

思ったよりも反応が良くて使いやすいです。

UIを日本語設定にすることができますが、この手の中華製品にありがちなひどい明朝フォントになったりはせず、普通に使えるレベルだと思います。とはいえ英語設定で使ってますが…

他に買ったものや欲しいもの

10dB アッテネータはマトモなやつを1つ買いました。追加でいくつか安いアッテネータを注文しています。

スペクトラム・アナライザによる高周波測定―高調波,不要輻射,変調,ひずみ,位相ノイズ,伝送特 (計測器BASIC) - 高橋 朋仁

高橋 朋仁

4.0 / 5.0

この本は買う前に読んでいました。アマゾンではなく、CQ出版社のサイトから PDF を買ってます

あとはリターンロスブリッジ (VSWR ブリッジ) が欲しいので作るかどうするか、という感じです。

方向性結合器は自分の欲しい周波数帯・耐電力のものは全く売っていないので作るしかありません。一度作っていますが、もっといいのを作りたいところです。

とはいえ50MHzまでならアンテナアナライザーもあるので、そこまで必要でもないかなという気がします。

LAN 経由でスクリーンショットをとる

まず測定画面のスクリーンショット(ハードコピー)ができないとかなり不便なので、ここから解決しました。

できたもの

プログレスを出しつつスクリーンショットをダウンロードしてこれます。ダウンロードしたあとは imagemagick の convertコマンドで png に変換し、optipng コマンドで最適化します。

ダウンロード部分にはライブラリ依存が一切ないので Ruby さえ動けば bmp ダウンロードはどこでも動きますし、LAN 経由なので一切ドライバなどが必要ないのが良いところです。

png 変換時には imagemagick コマンドと optipng コマンドが必要です。

測定器とのコミュニケーションプロトコル

DSA815 には USB 経由での GPIB 通信と、LAN ケーブル経由での VXI-11 (というよりは、LXIの一部)がサポートされています。

このへん、用語がいっぱいあるので整理しとくと

  • GPIB (IEEE488)
    • すごく古くからあるパソコンと計測器を繋ぐバス規格
    • 今は USB 経由に変換して繋ぐのが普通なようだ?
  • VXI-11
    • GPIB をイーサネット上で再現するような規格
  • LXI
    • LAN経由で計測器繋ぐというのをまとめた上位規格
    • Web インターフェイスとか、生TCPソケットとかの定義を含みつつ、VXI-11 も含む。
  • VISA
    • 計測器用プロトコルをひとまとめにした上位規格
    • VISA の裏側で GPIB とか VXI-11 とかが動いているイメージ

「というよりは、LXIの一部」と書きました。DSA815 はドキュメントには書いてないのですが、生のTCPソケット経由での通信もできるようになっていて、実はこれが一番簡単です。LXI の仕様では生TCPソケットの通信はポート5025を使うような記述がありますが、DSA815 では 5555 ポートです。

DSA815 でスクリーンショットコマンド

:PRIV:SNAP?

を送ると、11バイトのヘッダ(ファイル長を含む)とビットマップファイルが送られてきます。この :PRIV:SNAP? は隠しコマンドのようで、公式の Programming Reference にはありません。PRIV は Private の略なんでしょう。別のクライアントの通信からわかったので微妙なところです。

ref.

  1. トップ
  2. tech
  3. スペクトラムアナライザ DSA815-TG を買いました / LAN経由でスクリーンショットとれるようにしました

  • VIN 2〜24V
  • VOUT 2〜28V
  • 2A
  • 効率最大95%
  • スイッチング周波数1.2MHz

という表示のもの。

スイッチング周波数が高く、かなり小さい。(スイッチング周波数が高いほどコイルが小さくできるため。ただしスイッチング損が増えるので効率が犠牲になりやすい)。裏面に実装はなく、写っている部品で全て。

注意が必要で、普通と逆(左まわり)で電圧があがる。だから安いのか?

5V → 12V 昇圧での効率

2.5W → 2.2W 88%
5.0W → 4.73W → 94.6%
10W → 8.42W → 84.2%

これ以降出力低下

コイルが結構発熱する。

値段の割に効率が良く、小さいので結構汎用的に使えそう。2V〜 昇圧可能なのも嬉しい。NiMH 2本で昇圧したいときはベストといってもいいんじゃないかという気がする。

SX1308 とは

http://www.suosemi.com/ 深圳市硕芯科技有限公司という会社のもの

オン抵抗 80mΩ パワーMOSFETがビルトインされている。

  1. トップ
  2. tech
  3. 超小型 DC/DC ステップアップコンバータ SX1308 2A $0.99

パナソニック 口腔洗浄器 ジェットウォッシャー ドルツ 白 EW-DJ61-W - パナソニック(Panasonic)

パナソニック(Panasonic)

3.0 / 5.0

フロスをどうしてもサボってしまうので、11月ぐらいに買ってみました。だいたい使用感はわかったかなと思うのと、歯科での指導を挟んだのでで感想を書いておきます。

フロスの代わりにはならない

フロスが面倒で買ったのですが、ジェットウォッシャーではフロスの代わりにはなりませんでした。結局歯間の狭いところ(自分はほぼ隣同士の歯がくっついています)の歯垢は落とせないみたいです。かなり念入りにジェットウォッシャーを使ってからでも、フロスを使うとまださらに歯垢がとれます。歯科でのチェックでも、フロス使用時は95%〜ぐらいの歯垢除去率だったのが、ジェットウォッシャーだけの場合80%〜ぐらいまで落ちていました。

歯科のでチェックは歯石(磨き残しが続いて歯垢が固まった状態=恒常的に磨けていない箇所)も見るんですが、サボりがちのフロスでもひどいことにはならないので、サボりがちであってもフロスのほうがマシみたいです。

まぁそりゃそうだろという感じではあります。これは歯間ブラシの代替であって、フロスの代替ではありません。歯間が狭い人には向いてないのでしょう。

食べカスを落とすのは気持ちがいい

食べカスはすごい勢いで落ちてくるので気持ちが良いです。まぁフロスでも落ちるんですが、フロスの場合、糸にヘバりついてくる感じなので使用感が悪いという気持ちの問題があります。

冷い水でやるとつらい

冬の冷い水をすぐに使ってジェットウォッシャーをするとかなりつらいです。知覚過敏の人には苦行になります。

あらかじめて汲んでおいて室温にするとか、お湯でやるとかする必要があります……

今後

とはいえ、やらないよりはマシだと思うので、フロスと併用することにします。結局手順が増えてるだけじゃないかという……

「独自拡張のような形でIEEE802.11のフレームを飛ばして」と書きましたが、実際のWiFiパケットは見ておらず、真偽に疑問があったので、キャプチャして確認しました。

結論からいうとその通りのようで、Management frame (ビーコンとかと一緒) を Vendor-specific な形式で使っているようです。Wireshark だと Malformed Packet となってしまうのが謎ですが…


Management frame

Action field

を Vendor-specific で使っている

ただ、Vendor Specific Content にあたる部分に、esp_now_send で渡したバイト列以外にもヘッダみたいなのがついています。これはよくわかりません。

  1. トップ
  2. tech
  3. ESP-NOW はどのように IEEE802.11 フレームを使っているか?

OS X はビルトインで WiFI のパケットをスニフする機能が入っているので、とりあえずキャプチャして保存するだけならとても簡単にできます。

sudo /usr/libexec/airportd en0 sniff 1

これで WiFi のチャンネル1 (2.5GHz帯) のスニフができ、/tmp 以下に適当なファイルができて保存されます。

キャプチャはこれで良いのですが、別途解析ソフトウェアが必要です。

Wireshark

https://www.wireshark.org/

airportd sniff での結果は Wireshark で解析することができます。.cap ファイルを Wireshark の File → Open… から選択するだけです。

接続先が接続元の Mac アドレスでフィルタする場合、wlan.da == ff:ff:ff:ff:ff:ff や wlan.sa == ff:ff:ff:ff:ff:ff でできます。

該当するようなパケットが見つかっているなら、右クリックでフィルタとして適用とできるようになっています。

WPA2-PSK の解析

WiFi の経路は暗号化されているので、そのままだとIEEE802.11 フレームは読めますが、イーサネットフレームは読めません。しかしこれも Wireshark に適切に設定をすることで解析可能になります。

http://jorisvr.nl/wpapsk.html ここにSSIDとPassphrase を入れてでてくる16進数文字列をコピーしておきます。

Preferences… → Protocols → IEEE802.11 → Decryption Keys

type を wpa-psk として、Key にコピーした16進数文字列を入れます。

これで OK を押すと既存の解析結果も更新され、デコード可能ならデコードされます。

  1. トップ
  2. tech
  3. OS X での WiFi スニファ

オムロン 【自動電源ON】【4秒測定】【体重50g単位表示】【PC/スマホ対応 Wi-Fi通信機能搭載】体重体組成計 カラダスキャン HBF-253W-BK - オムロン(OMRON)

オムロン(OMRON)

5.0 / 5.0

なんとなく欲しいなとは思いつつ高いから買ってませんでしたが、妻が買ってきました。とりあえず結構いい感じです。

セットアップ時にスマートフォンのオーディオ出力とマイク入力を使ってやるようになってました。どういうタイミングで音を出してるのか調べなかったのですが、ヘルプだと「ピポパと聞こえたら…」という記述があるので、DTMF でやってるのかもしれません。このオーディオ出力とマイク入力を使う方法ですが、Zenfone2 ではうまくいった一方で、HTC J One ではうまくいきませんでした (beat audio などは切ってみたんですが…)

それはともかく、WiFi 経由ということですが、実際どのような通信が行われているのか知りたくなってきました。できれば接続先サーバをうまく騙して (ないし透過プロキシを通して) データをインターネットに出る前に自分で処理したいという気持ちもあります。

そんなわけでIEEE802.11フレームのパケットスニファを行って少しだけ追跡してみました。

接続先は vdu.wellnesslink.jp

WiFi パケットの解析をしてみたところ、ちゃんと TLS を使っているようでした。なので透過プロキシやら何やらを挟むことはできません。

体重データは人によってはセンシティブな個人情報ですから、TLS を使うのは当然でしょう。

一方、サーバサイドのサービスが終了してしまったとき、このデバイスの WiFi 機能は完全にゴミになります。自分の所有するデバイスの管理を自分でできないわけですからちょっと気持ち悪いところがあります。

  1. トップ
  2. tech
  3. オムロンのWiFi対応体重計 HBF-253W の通信傍受 (失敗)

pack のテンプレート文字列から、それを使ってパックした結果のサイズを求めたいということはありませんか。つまりやりたいことは sizeof(struct x) です。

pack テンプレート文字列は割と複雑で、任意長や文字列やポインタなどがあり、全てにおいてうまく動作するものを作るのは無理ですが、だいたいうまくいくのは実装できそうです。

sizeof(template)

def sizeof(template)
	x = Class.new(Numeric) do
		def to_str; ""; end
		def to_int; 0; end
		# implicit to_f is called only with Numeric subclass
		def to_f; 0.0; end
	end.new
	size = template.scan(/([a-zA-Z][_!]?[<>]?)([0-9]*)/).reduce(0) {|r,(_,n)|
		r + (n.empty?? 1 : n.to_i)
	}
	# p [template, size]
	Array.new(size) { x }.pack(template).size
end

やってることは「とりあえず pack してみる」ですが、pack するためには、まずテンプレート文字列に応じて、適当な型と適当な長さの配列が必要になります。

適当な長さという点では多い分には問題ないので、適当に数えています。

適当な型というのは、厳密にやると結局テンプレート文字列をパースするのと同じぐらい面倒なので、pack が要求するデータ型すべてに暗黙的に変換可能なオブジェクトを作っています。

pack が要求する型は整数・文字列・浮動小数点数があります。整数は to_int、文字列は to_str を実装すると、Ruby はそのオブジェクトについて整数・文字列と同等に扱う(必要なら暗黙的に変換される) ことになっています。

浮動小数点数にはそういったどんなオブジェクトも浮動小数点数として扱えるようにする、というメソッド名がないのですが、Numeric のサブクラスであって to_f が実装されている場合には暗黙的に変換されるというルールがあるので、ベースクラスを Numeric にしています。

[
	"C",
	"S",
	"L",
	"Q",

	"c",
	"s",
	"l",
	"q",

	"S_", "S!",
	"I", "I_", "I!",
	"L_", "L!",
	"Q_", "Q!",

	"s_", "s!",
	"i", "i_", "i!",
	"l_", "l!",
	"q_", "q!",

	"S>", "L>", "Q>",
	"s>", "l>", "q>",
	"S!>", "I!>",
	"L!>", "Q!>",
	"s!>", "i!>",
	"l!>", "q!>",

	"S<", "L<", "Q<",
	"s<", "l<", "q<",
	"S!<", "I!<",
	"L!<", "Q!<",
	"s!<", "i!<",
	"l!<", "q!<",

	"n",
	"N",
	"v",
	"V",

	"U",
	"w",

	"D", "d",
	"F", "f",
	"E",
	"e",
	"g",
	"G",

	"A",
	"a",
	"Z",
	"B",
	"b",
	"H",
	"h",
	"u",
	"M",
	"m",

#	"P",
#	"p",
#	"@",
#	"X",
#	"x",

	"C255",
	"i!2s!2",
	"i!i!s!s!",
].each do |tmpl|
	next if tmpl =~ /q/i
	puts "sizeof(%p) = %d" % [tmpl, sizeof(tmpl)]
end
sizeof("C") = 1
sizeof("S") = 2
sizeof("L") = 4
sizeof("c") = 1
sizeof("s") = 2
sizeof("l") = 4
sizeof("S_") = 2
sizeof("S!") = 2
sizeof("I") = 4
sizeof("I_") = 4
sizeof("I!") = 4
sizeof("L_") = 8
sizeof("L!") = 8
sizeof("s_") = 2
sizeof("s!") = 2
sizeof("i") = 4
sizeof("i_") = 4
sizeof("i!") = 4
sizeof("l_") = 8
sizeof("l!") = 8
sizeof("S>") = 2
sizeof("L>") = 4
sizeof("s>") = 2
sizeof("l>") = 4
sizeof("S!>") = 2
sizeof("I!>") = 4
sizeof("L!>") = 8
sizeof("s!>") = 2
sizeof("i!>") = 4
sizeof("l!>") = 8
sizeof("S<") = 2
sizeof("L<") = 4
sizeof("s<") = 2
sizeof("l<") = 4
sizeof("S!<") = 2
sizeof("I!<") = 4
sizeof("L!<") = 8
sizeof("s!<") = 2
sizeof("i!<") = 4
sizeof("l!<") = 8
sizeof("n") = 2
sizeof("N") = 4
sizeof("v") = 2
sizeof("V") = 4
sizeof("U") = 1
sizeof("w") = 1
sizeof("D") = 8
sizeof("d") = 8
sizeof("F") = 4
sizeof("f") = 4
sizeof("E") = 8
sizeof("e") = 4
sizeof("g") = 4
sizeof("G") = 8
sizeof("A") = 1
sizeof("a") = 1
sizeof("Z") = 1
sizeof("B") = 1
sizeof("b") = 1
sizeof("H") = 1
sizeof("h") = 1
sizeof("u") = 0
sizeof("M") = 47
sizeof("m") = 0
sizeof("C255") = 255
sizeof("i!2s!2") = 12
sizeof("i!i!s!s!") = 12
  1. トップ
  2. tech
  3. Ruby の pack テンプレート文字列からそのデータサイズを求める
  1. トップ
  2. ruby
  3. Ruby の pack テンプレート文字列からそのデータサイズを求める

Cの構造体とかだと、構造体の中に他の構造体ということは普通にあります。

こういった構造体の文字列を unpack すると、全部フラットな配列になってしまうので、Ruby レベルのオブジェクト構造として再構成しようと思うと面倒なことになります。

ということで、入れ子に対応した unpack というのを実装してみました。

概要

以下のような感じで {} (ブレース) で入れ子を表現するようにテンプレート文字列を拡張します。

struct_foo_t = %{
	I!
	I!
	Z16
}

struct_bar_t = %{
	I!
	I!
	{
		#{struct_foo_t}
	}
	I!
} #=> "I!I!{I!I!Z16}I!"

original = [
	0xffff,
	0xfeff,
	[
		0x11,
		0x22,
		"foobar"
	],
	0x10,
]

packed = original.pack_deeply(struct_bar_t)
p packed
#=> "\xFF\xFF\x00\x00\xFF\xFE\x00\x00\x11\x00\x00\x00\"\x00\x00\x00foobar\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00"

unpacked = packed.unpack_deeply(struct_bar_t)
p unpacked
#=> [65535, 65279, [17, 34, "foobar"], 16]

p original == unpacked
#=> true

実装

先頭から unpack して入れ子を処理していくというイメージです。処理済みのバイト長が欲しいので、クソみたいですが一旦 unpack したものをもう一度 pack しています。

pack_deeply は単にフラットにして pack するだけなので簡単です。

class String
	def unpack_deeply(template)
		ret = []
		tree = [ ret ]
		n = 0
		tmpl = ""

		# unpack partial and add to result
		unpack = lambda {
			unpacked = self.slice(n..-1).unpack(tmpl)
			# re-pack to get byte length
			size = unpacked.pack(tmpl).size
			n += size
			tmpl = ""
			tree.last.concat(unpacked)
		}

		template.each_char do |chr|
			case chr
			when "{"
				unpack.call
				ary = []
				tree.last << ary
				tree << ary
			when "}"
				unpack.call
				tree.pop
			else
				tmpl << chr
			end
		end
		unpack.call
		ret
	end
end

class Array
	def pack_deeply(template)
		flatten.pack(template.gsub(/[{}]/, ''))
	end
end
  1. トップ
  2. tech
  3. Ruby でネストした構造体文字列をネストした配列として unpack する
  1. トップ
  2. ruby
  3. Ruby でネストした構造体文字列をネストした配列として unpack する

markdown で HTML にして wkhtmltopdf で PDF 化する汎用 Makefile です。

Markdown で書いてプレビューするのはいいんですが、画像がちらばってしまって面倒です。PDF にしておけば1ファイルにまとまるのと、だいたいどこでも見ることができるので便利です。

# brew install markdown
# http://wkhtmltopdf.org/downloads.html

.SUFFIXES: .md .html
.SUFFIXES: .html .pdf

SRCS = ${wildcard *.md}
HTMLS = $(SRCS:.md=.html)
PDFS = $(SRCS:.md=.pdf)

define header
	<!DOCTYPE html>
	<meta charset="utf-8">
	<title>MD</title>
	<style>
	body { font-family: sans-serif; line-height: 1.66 }
	img { max-width: 800px }
	</style>
endef
export header

.PHONY: all
all: $(PDFS)

$(HTMLS): Makefile

.html.pdf:
	wkhtmltopdf $< $@

.md.html:
	(echo "$$header"; markdown $<) > $@
  1. トップ
  2. tech
  3. Markdown → PDF にする Makefile