トランジスタ技術 2016年 2月号 - トランジスタ技術編集部

トランジスタ技術編集部

3.0 / 5.0

ここ1ヶ月ぐらい GPSDO に興味があって調べていたのですが、先月のトランジスタ技術がまさにGPSの特集だったようでした。

なんか知らずに流行りに乗ったみたいでかっこわるい…… というのはおいといて、おもしろそうなので買って読んでみました。

GPSDO については1記事だけでした。しかしMCUなしでの製作という、男らしい感じなのですが、残念ながらあんまり参考になりませんでした…… 使っているモジュールはちょっと前に買ってみたNEO-6Mで親近感がありました (というか1PPS出せて安いのをebayで探すとこれが最安なので第一候補になるわけですが)

ublox のモジュールは TIMEPULSE ピンに 1PPS に限らず安いものでも10kHzぐらいまで可変で出せるのですが、製作記事でもこの機能を使っていました (MCUを使わないことによる副作用として)。個人的にはこの機能を使うのはいまいちというか、他のGPSモジュールとの互換性がなくなるのがなあと思ってしまいます。

読んでていて疑問に思った点

  • 出力にバンドパスフィルタをつけてサイン波にしているのは?
    • 普通はサイン波で出すから?
    • 高調波成分が含まれるのが嫌?
    • 矩形波出力で立ちあがりを高速にしたほうがクロックとして有利なイメージがあるけどそうではない?

そもそも基準クロックというのに疎いので根本的なところがよくわかっておらず難しい。

出力を4分配にしているところで、バッファ+トロイダルコアを4つ使って分配しつつ直流的に?アイソレーションしているみたいでしたが、1つのバッファアンプ+ハイブリッドのほうが部品点数は減りそうだけど、うまくいかないのだろうか?とか机上の空論を考えてました。

精密なAD変換器のような非常に敏感な回路などではこの問題を避けるために、矩形波の代わりに正弦波をタイミング基準として使用する。

https://ja.wikipedia.org/wiki/%E7%9F%A9%E5%BD%A2%E6%B3%A2

Wikipedia にも書いてありましたが、クロックの高調波の影響を排除するために正弦波を使うんですね。無知でした。

その他

現在 GPS に載っている発振器のほとんどがルビジウムオシレータでびっくりしました。なんとなく全てセシウム原子時計が載っていると思い込んでいました。

AD9851 は Arduino で一度動かしてみましたが、LPC1114/mbed な環境でも動かしてみました。


DDS モジュールの定格は5Vですが、3.3Vで動かしてみています。振幅は当然減りますが、ちゃんと70MHzぐらいまでは波形が確認できました。

コード

Arduino のコードとほぼ一緒です。というか殆ど正規表現で置換しただけで動かすことができました。GPIO の操作だけなので本当に頭を一切使わずに移植できました。

GCC かつ -std=c++14 な環境なため mbed のオンラインコンパイラではコンパイルできないと思います (すこし修正すればいけるはずですが C++14 が使えない環境に興味がないので……)

また、なんとなくシリアル経由で周波数を変えるインターフェイスにしてみました。mbed のライブラリに CircularBuffer があるので簡単です。C++ の STL に circular buffer / ring buffer 相当のものがないのが不思議なんですが、いつか入る予定はあるんでしょうかね?

#include "mbed.h"
#include <CircularBuffer.h>
#include <cstdlib>
#include <string>

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

	DigitalOut PIN_DATA;
	DigitalOut PIN_FQ_UD;
	DigitalOut PIN_W_CLK;
	DigitalOut PIN_RESET;

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

		// control bits
		PIN_DATA = MULTIPLIER ? 1 : 0;
		PIN_W_CLK = 1; wait_us(4);
		PIN_W_CLK = 0; wait_us(4);
		PIN_DATA = 0;
		PIN_W_CLK = 1; wait_us(4);
		PIN_W_CLK = 0; wait_us(4);

		// powerdown
		PIN_DATA = powerdown ? 1 : 0;
		PIN_W_CLK = 1; wait_us(4);
		PIN_W_CLK = 0; wait_us(4);

		// phase
		for (int i = 0; i < 5; i++) {
			PIN_DATA = (phase>>i & 1);
			PIN_W_CLK = 1; wait_us(4);
			PIN_W_CLK = 0; wait_us(4);
		}

		PIN_FQ_UD = 1; wait_us(4);
		PIN_FQ_UD = 0; wait_us(4);
	}

public:
	AD9851(
			PinName data,
			PinName fq_ud,
			PinName w_clk,
			PinName reset
		) :
			PIN_DATA(DigitalOut(data)),
			PIN_FQ_UD(DigitalOut(fq_ud)),
			PIN_W_CLK(DigitalOut(w_clk)),
			PIN_RESET(DigitalOut(reset))
	{
		PIN_DATA = 0;
		PIN_FQ_UD = 0;
		PIN_W_CLK = 0;
		PIN_RESET = 0;
	}

	/**
	 * W0 ... W31  -> Freq (LSB first)
	 * W32, W33    -> Control (for factory test)
	 * W34         -> Power-Down
	 * W35 ... W39 -> Phase (LSB first)
	 */

	void reset() {
		// ensure low
		PIN_DATA = 0;
		PIN_FQ_UD = 0;
		PIN_W_CLK = 0;

		// reset
		PIN_RESET = 1; wait(1);
		PIN_RESET = 0; wait(1);

		// reset to serial mode
		// Pins of D0, D1 = 1, D2 = 0 for serial mode
		PIN_W_CLK = 1; wait_us(4);
		PIN_W_CLK = 0; wait_us(4);

		PIN_FQ_UD = 1; wait_us(4);
		PIN_FQ_UD = 0; wait_us(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(/*data*/dp28, /*fq_ud*/dp26, /*w_clk*/dp25, /*reset*/dp1);

//AnalogIn adc1(dp9);
//AnalogIn adc2(dp10);
//AnalogIn adc3(dp11);
//AnalogIn adc4(dp13);

Serial serial(USBTX, USBRX);

CircularBuffer<char, 80> serial_buffer;

int main() {
	serial.baud(115200);
	serial.printf("init\n");

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

	for (;;) {
		if (serial.readable()) {
			char c = serial.getc();
			if (c == 0x0D) continue; // ignore CR
			if (c == 0x0A) { // treat LF as line end
				std::string line(80, '\0');
				line.clear();

				char c;
				while (serial_buffer.pop(c)) {
					line += c;
				}
				serial_buffer.reset();

				serial.printf("GOT: %s\n", line.c_str());

				uint32_t new_freq = std::atoi(line.c_str()) * 1e6;
				ad9851.set_frequency(new_freq);
				continue;
			}

			serial_buffer.push(c);
		}
	}
	return 0;
}

メモ: mbed 移植正規表現

S/digitalWrite\(([^,]+), ([^)]+)\)/\1 = \2/
s/delayMicroseconds/wait_us/g
s/delay/wait/g
  1. トップ
  2. tech
  3. AD9851 DDS モジュールを LPC1114/mbed と

アマゾンプライムビデオがはじまったのはもちろん知ってはいましたが「PC で見れてもなぁ」と思ってました。PC で動画を見る習慣がないので結局見ません。

しかし PS4 で直接見れることに気付いて、ただただ、最高という気持ちに変わりました。

まぁアマゾンプライムビデオとかは些細なことで、未確認で進行形を見なおしてるんですが、最高です。最高です。小紅は最高に可愛い。

1000円ぐらいで買いました。このモジュールはアンテナが分離型で、1PPS 出力がとれるという特徴があります。

ublox というメーカーのGPSチップが載っています。u-center というソフトウェアから詳細な設定ができることになっています。ただし u-center は Windows のみです。

GPS Fix に時間がかかる

窓際で受信させてみましたが、いつまで経っても GPS Fix しません。u-center でしばらく眺めていると1時間〜2時間ぐらいでようやく GPS Fix しました。

スマフォ内蔵のGPSだと窓際でもすぐGPS Fixできるので、ちょっと残念なところです。

1度3D Fixまでいけば、割と安定して受信が継続されました。

NEO-6M は GPS(アメリカ)のみに対応していて、GLONASS (ロシア)やQZSS(日本)は対応していないので、そのせいかもしれません。

1PPS

自宅でGPS使って位置を知れてもあんまり意味がありません。このGPS モジュールの最大の目的は1PPSです。

1PPS (Pulse Per Second) 出力はLED が接続されており、GPS Fix 時には1秒ごとにLEDがフラッシュします。

この1PPSはGPS衛星に搭載されている原子時計と同期しているので、とても正確です。ただし、GPSモジュール内蔵クロックが48MHzなので最大42nsぐらいのジッタはありそうです。そこそこに長い目で見ると原子時計並に正確な1Hzが得られるというものです。

スペック的には以下のようになっています(いまいち各項目の意味がわかりませんでしたが…)

モジュールのピンヘッダに出ていないので、必要なら直接引き出す必要があります。

• For best timepulse performance it is recommended to disable the SBAS subsystem. 

日本にはSBASシステムがそもそもないので完全に無効にしときましょう。

NMEA を Ruby で雑に読んでみる


特にライブラリとかを使わずに、ちゃんと動いているかだけのテストをできるようにスクリプトを書きました。動かすと以下のような出力になります。

2016-02-16 15:28:5.000Z VALID:YES / 3D Fix [1: 31dB*] [3: 21dB*] [7: 26dB*] [8: 0dB] [10: 0dB] [11: 15dB*] [16: 15dB*] [17: 0dB] [22: 0dB] [27: 0dB] [28: 19dB*] [30: 0dB*]
#!ruby -v
require 'serialport'

@port = SerialPort.new(
	"/dev/tty.usbserial-A50285BI",
	9600,
	8,
	1,
	0
)

cols = `tput cols`.to_i

status = {
	using: [],
	sates: {}
}

loop do
	while line = @port.gets
		type, *rest = line.chomp.split(/,/)
		next unless type[0] == '$'
		_ = rest.pop
		case type
		when "$GPRMC"
			h, m, s = *rest[0].match(/(..)(..)(.+)/).captures.map {|i| i.to_f }
			dd, mm, yy = *rest[8].match(/(..)(..)(..)/).captures.map {|i| i.to_i }
			datetime = "%04d-%02d-%02d %02d:%02d:%02.3fZ" % [yy + 2000, mm, dd, h, m, s]
			state = rest[1]
			status[:UTC] = datetime
			status[:is_valid] = state == "A"
		when "$GPGGA"
			sate_count = rest[6]
			status[:sate_count] = sate_count
		when "$GPGSA"
			mode = rest[0]
			type = rest[1] # 1 = invalid, 2 = 2d, 3 = 3d
			sate_nums = rest[2, 12]
			status[:mode] = mode
			status[:type] = type
			status[:using] = sate_nums.map {|i| i.to_i }
		when "$GPGSV"
			total = rest.shift
			current = rest.shift
			if current == 1
				status[:sates] = {}
			end
			_ = rest.shift # count
			until rest.empty?
				num = rest.shift
				e = rest.shift
				d = rest.shift
				sn = rest.shift
				status[:sates][num] = {
					num: num.to_i,
					e: e,
					d: d,
					sn: sn.to_i,
				}
			end
			if total == current
				# done
			end
		else
			# ignore
		end
		out = "%s VALID:%s / %s Fix %s" % [
			status[:UTC],
			status[:is_valid] ? "YES" : "NO",
			status[:type] == "1" ? "NO" : "#{status[:type]}D",
			status[:sates].values.sort_by {|i| i[:num] }.map {|i|
				"[%s: %ddB%s]" % [
					i[:num],
					i[:sn],
					status[:using].find_index(i[:num]) ? "*" : ""
				]
			}.join(" ")
		]
		print "%- #{cols-1}s\r" % out
		$stdout.flush
	end
end
  1. トップ
  2. tech
  3. ublox NEO-6M GPS Module

スマフォで見たとき、ファーストビューがあまりにダサすぎたのでヘッダ部分をちょっとマシにしました。

日記のタイトル部分は SVG にしてみました。そろそろ常用してもいいかなという気持ちです。


PC 側もちょっと変えていて、本文の幅を広告と一致するようにしました。なんで今までズレたまま放っていたのが謎ですがようやくすっきりした気がします……

DIP で唯一実用になる(?) ARM Cortex-M0 マイコンです。SRAM が 4KB しかないのが心許ないですが、秋月で現在180円と、性能の割に激安です。

32bit ARM なので、32bit 演算が多少出てくるような場合は AVR より圧倒的に良い選択そうです。(FPU はありませんが)。また mbed は Arduino よりもライブラリデザインがマトモという印象があります。

platformio でコンパイルして、シリアルアダプタ経由で ISP 書きこみするというのを試しました。フレームワーク(ライブラリ)としては mbed を使っていますが、mbed の開発環境は使っていません。

platformio での開発

platformio.ini を以下にようにして

[env:lpc]
platform = nxplpc
framework = mbed
board = lpc1114fn28
build_flags = -std=c++1y

main.cpp を以下にようにしました。

#include "mbed.h"

DigitalOut led(LED1);

int main() {
	while(1) {
		led = 1;
		wait(0.5);
		led = 0;
		wait(0.5);
	}
	return 0;
}

この状態で platformio run をすると .pioenvs/lpc/firmware.bin にコンパイル済みバイナリができます。

platformio は mbed の開発環境を前提としているようで、upload では単に upload_port 先のディレクトリにコピーをしようとします。今回はシリアル経由で書きこむので、platformio 経由での upload は使いませんでした。

ピンアサイン

https://developer.mbed.org/platforms/LPC1114FN28/ このページを見るのが一番良いようです。

上記コード中で指定している LED1 は左下の dp14 になります。

書きこみ

書きこみツールは lpc21isp を使うのが一番簡単なようです。展開して make するだけで OS X でも普通に動きました。

https://sourceforge.net/projects/lpc21isp/files/lpc21isp/ からダウンロードできます。

以下にような感じで書きこめました。115200 は書きこみ時のボーレート、12000 はMCUの動作周波数をkHz単位で指定するようです。内蔵RCは12MHzなので12Mhzを指定しています。が、mbed 実行時は逓倍して48MHzで動いているようなので、どっちを指定するのかよくわかりません。

lpc21isp -control -bin .pioenvs/lpc/firmware.bin /dev/tty.usbserial-A50285BI 115200 12000

ここでは -control を指定しています

         -control     for controlling RS232 lines for easier booting
                      (Reset = DTR, EnableBootLoader = RTS)

となっており、この通りにシリアル変換の DTR を nR (RESET) 、RTS を dp24 に接続することで、自動リセットしてISPモードへ移行して書きこめます。冒頭の写真の通りの配線です。

追記:シリアル通信を行う

上記の方法だと、普通にシリアル通信を行おうとするとRTS/DTRまわりの挙動によってリセットが発生してISPモードに入ってしまうことがあります。なので普通に screen などでシリアル出力を見ることができません。これは RESET の配線を切れば問題ないのですが面倒くさいところです。

実はこれもlpc21ispを使って以下にようにすると、配線を変えずにシリアルモニタができるようです。

lpc21isp -termonly -control /dev/tty.usbserial-A50285BI 115200 12000

コード例

#include "mbed.h"

Serial serial(USBTX, USBRX);
DigitalOut led(LED1);

int main() {
	serial.baud(115200);

	while(1) {
		serial.printf("Hello, World!\n");
		led = 1;
		wait(0.5);
		led = 0;
		wait(0.5);
	}
	return 0;
}

ref.

  1. トップ
  2. tech
  3. LPC1114 LPC1114FN28 / mbed 開発を platformio を使ってやる

SDカードなどを接続したとき、使ってもいない写真.appがいちいち起動してだいぶ鬱陶しいです。以下のコマンドで止めることができました。

defaults -currentHost write com.apple.ImageCapture disableHotPlug -bool YES

昔は Image Capture.app の環境設定からデフォルトの設定が変えられたはずなんですが、El Capitan ではその機能が消滅していて、GUI で変更する方法がないようです。クソですね。

  1. トップ
  2. tech
  3. El Capitan になってから、Mac の写真.appの自動起動がガチでうざい

普段からほとんど常時に「バカにされている」と感じている。基本的には特定の誰かというわけではない。ただしコンテキストによっては特定の誰かの顏が頭に浮かんでいることもある。とはいえそれも、特定の誰かが事実として常に罵倒してくるわけではない。

普通に生きていると、自分の意見というのは、ほとんど尊重されない。自分の中でこの感覚が消化された結果「バカにされている」という主観ができあがるのだろうと思う。

「尊重されなさ」はあらゆることで感じられる。

煙草、特に路上喫煙に殺意(文字通り。)が沸くのはなぜかといえば、臭い煙に困っている自分が尊重されていないと感じるからだと思う。ここで大事なのは、実際にそれをしている喫煙者が尊重してくれないということではなく、そういう喫煙者の存在を許していて、なおかつ自分の存在を許さない(と主観的に感じる)社会が尊重してくれない、ということである。

社会に自分が尊重されていないというのは、自分の存在が否定されていることと同じことです。

自分の中で全ての「尊重されなさ」は社会と通じて繋がっている。仕事で尊重されなくても、道を歩いていて尊重されなくても、それらは繋がっており、自分の中で消化され、バカにされたと感じるわけです。