http://www.rubycon.co.jp/products/alumi/technote3.html#3-7
https://www.nichicon.co.jp/lib/aluminum.pdf

「放置特性」という形で言及されていることがある。放置していると、漏れ電流が増加していく。もともと経年劣化で必ず増加するが、電圧がかかっていれば自動修復されるものが、放置状態だとこれが行われないということのようだ。「電圧処理」をすれば修復されて元に戻る。

また経時劣化で内部圧力が高まり、液漏れすることがある。これも無負荷でも発生する。この場合、膨らんでいるコンデンサは「消費期限切れ」なので捨てるしかない。

  1. トップ
  2. tech
  3. 電解コンデンサは常温放置でも劣化する

https://github.com/pcb2gcode/pcb2gcode/wiki/Options:-Alignment-for-two-sided-PCB-and-offsets

mirror-axis まわりが追加された?ようで、デフォルトの挙動が変わってしまっていた。かなりハマった。しかもこれ、面倒なことにプロジェクトごと(ボードの大きさごと)に決められた値を入れないと今までと同じ挙動にならない。

mirror-axis には width の半分を入れる。一回変換してみて表示される数字を半分にわればいいが……

ケースのタカチの製品検索 ( https://www.takachi-el.co.jp/search/detail ) で結果をソートする方法がないので、JS でやる。

JS コンソールで以下を実行する。

p = $x('//*[@id="mCSB_1_container"]/table/tbody')[0];$x('tr[@class="child"]', p).sort( (a, b) => $x('number(./td[9])', b) < $x('number(./td[9])', a) ? 1 : -1 ).forEach( (e) => p.appendChild(e));

  • RGB BGR の設定は正しいか
  • 0xdddddd がグレーで表示されるか?
    • されない場合 GBRG / GRBG の可能性あり

ドライバが同じでも、載っている液晶のデータ形式が違うことがある。

備考 RGB888 (24bit) から RGB565 (16bit) へ変換

RGB

#define RGB565(rgb888)     ( ((( (rgb888)&0xf80000)>>8) | (((rgb888)&0xfc00)>>5) | (((rgb888)&0xf8)>>3)) )

GRBG

#define RGB565(rgb888)     (( (rgb888) & 0x1c00) << 3)|(( (rgb888) & 0xf8) << 5)|(( (rgb888) & 0xf80000) >> 16)|(((rgb888) & 0xe000) >> 13)
  1. トップ
  2. tech
  3. SPI TFT 液晶 (ST7735など) で色がおかしいときに試すこと


クリスタルラダーフィルタとクリスタルの特性測定

  • LCR メーターで容量 Cp を測る
  • VNA で直列共振周波数・並列共振周波数を測る
    • VNA の CH0/CH1 に直列で接続する
  • VNA でR を測る。直列共振周波数付近で読む
    • VNA の Ch0 に接続して読む。狭スパンで見ればスミスチャート上の等Rにそって表示されるはず

f_s = 9.997753
f_p = 10.015550
R = 10.7Ω
C_p = 1.67pF

C_s = C_p * ( (f_p**2) / (f_s**2) - 1) //=> 5.950825783949261e-15 (5.951fF)
L = 1 / (4 * (Math.PI ** 2) * (f_s ** 2) * C_s) //=> 0.04258515389890184 (42.6mH)

これをもとに LTSpice などで等価回路を入力する。LTSpice の場合、xtal の記号はあるので、これ使うのが良い。といっても実体は C そのもので、右クリックで上記で計算した詳細パラメータを入力することで xtal の等価回路になる。

備考

C_s は fF (フェムトファラド) 単位と非常に小さいので、直接は測れない。C_p は pF、R は数Ωオーダーになる

  1. トップ
  2. tech
  3. クリスタルラダーフィルタ

BluePill は VBUS と 5V と書かれた端子が直結しており、5V ピンに給電してセルフパワー化しようとしたときにUSB接続すると危険です。なので5Vピンに自力で給電したい場合、これを切り離して純粋なセルフパワー化する必要があります。

基板上のカットポイントは写真の通りです。ここをカットすれば、基板上の 3.3V LDO を生かしたまま VBUS だけを分離し、5V ピンから給電できるようになります。

模倣チップ

ところで STM32F103C8 激安ボード (BluePill) ですが、載っているチップが中華製の模倣チップであることが多々あるようです (というより種類が異なる模倣チップがいくつかある?)。模倣チップでも動けばいいじゃんという話でもなく、SWG 経由で得られる coreid が本物と一致せず、若干面倒な手順を踏まないと書きこめないことがあります。

買う前に見分ける方法はあまりなく、親切な業者ならよく読んでみると STM32F103C8 とともに CKS CS32F103C8T6 と書いてあったりすることがあります。まず説明ページをよく読んでおく必要があります。GigaDevice GD32F103C8 というのもありますが、これは coreid も STM32 と同様のようです(未確認)。

ICをけずってリマークしてある悪質なものも存在しています (ebay で買ったらそうだった)。ダメだった場合に雑に dispute できるので、値段もかわらないし ebay よりも Aliexpress がおすすめ。

  1. トップ
  2. tech
  3. STM32F103C8 激安ボード (BluePill) のセルフパワー化

Sパラメータは「ある基準インピーダンスのとき」のパラメータなので、50Ωとか75Ωとか、基準が決まっていないといけない。Sパラを表記するときには基準インピーダンスの表記も必須。

Zパラメータの場合はSパラメータ+基準インピーダンスの情報を含んでいる。ZパラメータはSパラメータの基準インピーダンスの逆数(基準アドミッタンス)が0の場合と考えることもできる。「じゃあ最初からZパラメータで示したほうがよくない?」という気がするが、基準に0を使っているので「Zパラメータでは表現できない」というケースがあり、万能ではない。0除算が発生しないSパラメータはどんな回路でも必ず存在するが、Zパラメータはそうとは限らない。

出力インピーダンスはどれ?

Sパラだけではわからない。さらに前の信号源インピーダンスが影響する

Zパラメータの意味

2ポートの場合のZパラメータの意味は以下の通り

  • : 端子2を開放したときの、端子1から見たインピーダンス
  • : 端子1を開放したときの、端子2から見たインピーダンス
  • : 端子1を開放したときの、開放端電圧と端子2の流入電流との比
  • : 端子2を開放したときの、開放端電圧と端子1の流入電流との比

ref.

https://www2-kawakami.ct.osakafu-u.ac.jp/osakafu-content/uploads/sites/407/Lecture_Data/Electric_Circuit_II/01_Lecture/02_lecture_electric_circuit_II.pdf
https://home.hiroshima-u.ac.jp/amakawa/files/amakawa_S-parameter_20180528_1.pdf

  1. トップ
  2. tech
  3. SパラメータとZパラメータのメモ

回路と制御方法はだいたい決めたのでそのへんを書いておく

VFC をどうコントロールするか

OCXO がいくら安定しているといっても、それはVFC(Voltage Frequency Control)次第で、短期的にはこれが十分に安定していなければいけない。

手元の OCXO (MORION MV102) は Uref と呼ばれる端子から 5V の基準電圧 (OCXO 内で安定化された電圧源) が出ており、これを使って VFC へ入力する電圧を生成するようになっている。VFC は 0〜5Vの範囲で ±5Hz ぐらいの調整ができ、約2.5V付近で10MHzになる。

PWM + ローパスフィルタ

最初は MCU 内蔵の 16bit PWM をローパスフィルタにかける方法を試していた。やることは簡単で、PWM の出力でFETを駆動してレベルシフトしつつ一定の範囲に調整して、その結果をサレンキー型のアクティブローパスフィルタ(1倍)に通すというもの。

これでいいはずなんだけど、部品点数が増え、誤差の扱いがよくわからなくなるのでかえって難しく、やめてしまった。ちゃんとシミュレーションすれば、これで十分な結果は出せるはずだとは思う。特にレベルシフトに使う FET のオン抵抗温度特性がよくわからなかった。

現在の回路

(この回路図に出力フィルタは含んでいない)

12bit DAC


DAC だとスペックが決まっているので、誤差に対する考えかたはいくぶん簡単になる。ただ、抵抗ラダー型のDACは性質上リニアリティに劣るのと、12bit ぐらいまでしか気軽に手に入らないので出力可変範囲が狭くなる。

2.5V±0.25V (0.5V範囲) ぐらいを可変するように抵抗分圧回路を設計して、この範囲を12bitで調整する。つまり 0.12mV/count。手元のOCXOは2Hz/Vなので、結果として0.24mHz 単位で調整できるようになる。

今回使うのは、MCP4725。温度特性は Offset Error Drift が ±1ppm/℃、Gain Error Drift が -3ppm/℃。出力は 0〜5Vの範囲で、これを0.5Vの範囲に圧縮しているので、Offset Error Drift は ±0.00000024mHz/℃ Gain Error Drift は -0.00000072mHz/℃ となり、ほぼ無視できる。

1PPS の精度と確度

GNSSモジュールの 1PPS の UTC に対する精度はスペック上、99% で ±60ns (60e-9) と書いてあった。これは1秒あたりのジッタなので、10秒なら 6e-9 100秒で6e-10(0.6ppb)になる。

10MHz をカウントする場合は100秒で±6mHz、1000秒で±0.6mHzの確度があることになる。

以下で書くように周波数カウントの確度のほうが低いので、こちらは無視できる (はず)。

周波数カウントの確度

ゲート時間1秒だと、周波数測定確度が1Hz単位しかない。ゲート時間を徐々に長くしていき、ゲート時間1000秒で1mHzの確度(1e-10 = 0.1ppb)が得られる。

±1カウント誤差(量子化誤差)があるため、±1のカウント誤差は無視し、±2以上の誤差が発生したら補正をかけるようにする。この積分時間には特に上限を設けず時間に応じて補正値を計算する。

OCXOの温度安定性

スペック上、使っているOCXOの温度安定性は最大±2e-10 (-55〜80℃) (0.2ppb)。温度変化で最大2mHzは変化する可能性がある。この温度範囲でこの変化量なのでかなり変動は少ない。

↑のスペックは一番良いモデルの場合のようで、一番下のモデルだと(手元のがどのモデルか不明なので)、±5e-9 (0〜55℃) のようだ。つまりこの範囲で50mHzほど変動する可能性があり、雑に計算しても1℃で約1mHzほど変化するかもしれない。

抵抗分圧の温度安定性


抵抗の温度特性で気軽に手に入る±100ppm/℃の場合をLTSpice でシミュレーションしてみると、VFC は最悪 ±0.25mV/℃ 変動することがわかる。これは±0.5mHz/℃ に相当する。5℃変化すれば±2.5mHz動くことになる。最悪というのは全部の抵抗の温度特性が正負も含めてバラバラというケースなので実際はもっとマシだとは思うが、ちょっと大きすぎる。

±10ppm/℃であれば出力温度係数は1桁減って、約±0.025mV/℃、±0.050mHz/℃ になる。PPS によって制御されているとはいえ、1000秒(16分)ぐらいの頻度でしか調整できないので、ここはできるだけ温度安定性を上げておきたい。

温度安定性のまとめ

  • OCXO ±50mHz (0〜55℃)
  • DAC Offset Error Drift は ±0.00000024mHz/℃ Gain Error Drift は -0.00000072mHz/℃
  • 抵抗分圧 (TCR=±10ppm) ±0.050mHz/℃

1PPS は 1000秒(約16分)で±0.6mHzの確度。あとは室温の安定性で調整する頻度などが変わる。

経過

温度は MCU 内蔵の精度の低いものを利用しているので、相対的な傾向ぐらいしかわからない。また、MCU と OCXO 自体は熱結合しているわけではない (基本的には室温を計測する意図)。error_mHz はn平均で何mHzの誤があるか。temp はMCUの温度、sum は誤差カウントの蓄積、dac は実際に DAC に与えている値、デバッグ用の無意味なグラフが含まれているので注意(drift)

アラン偏差も出しているが1000秒未満は見てもしかたなく (PPSの精度的に)、1000秒以上も意味がある値かどうかよくわからない。まぁさすがにGPSにロックしているので長期ドリフトがない(右上がりにならない)ぐらいは確認できていると思う。

備考: 精度

1e-10 の精度 (1mHzオーダー)であれば (1/1e-10) / (60*60*24*365) = 317年で1秒しか狂わない。

  1. トップ
  2. tech
  3. GPSDO 10MHz 回路と温度安定度

そういえば作ってあった。いろいろ説明が不足しているが C のコードを吐く機能がある。そのうちなんとかしたい。

  1. トップ
  2. tech
  3. BDF Font のグリフを一覧で見るやつ

既にある NMEA のログから、どの衛星が強く受信できているかをプロットしてみる。

ファイルを指定して png を書き出すコードを node で書いた。かなり量の多いログでも中間結果を見ながら生成できるようにしてある。

グラフの見方

上下左右が方角。右まわりに上から北・東・南・西

中心からの距離が仰角。中心が真上(90°)で、円周上が水平線(0°)

SNR によって色がついている。

自宅の状況

GPS の GSV しかとっていないので、このプロットにはGLONASSやQZSSは含まれていない。

南西向きの窓際にアンテナがあるため、そのあたりのSNRが大きくなる。

北極・南極の上空は衛星の軌道がない。北半球に住んでいる場合、このプロットのように北極上空にあたる部分に空白ができる。

決まった数の衛星が決まった軌道で飛んでいるので、空全体で見ると塗りつぶされないところは多い。

コード

#!/usr/bin/env node

const GPS = require("gps");
const fs = require("fs");
const readline = require("readline");
const { createCanvas, loadImage } = require('canvas');

const WIDTH = 1000;
const HEIGHT = 1000;
const PADDING = 50;

const PLOT_RADIUS = (WIDTH - PADDING * 2) / 2;

const canvas = createCanvas(WIDTH, HEIGHT);
const ctx = canvas.getContext('2d');
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, 1000, 1000);

ctx.translate(WIDTH / 2, HEIGHT / 2);

ctx.strokeStyle = "#999999";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(0, 0, PLOT_RADIUS, 0, 2 * Math.PI);
ctx.stroke();

ctx.beginPath();
ctx.moveTo(0, -PLOT_RADIUS);
ctx.lineTo(0, +PLOT_RADIUS);
ctx.moveTo(-PLOT_RADIUS, 0);
ctx.lineTo(+PLOT_RADIUS, 0);
ctx.stroke();

ctx.font = "20px sans-serif";
ctx.fillStyle = "#666666";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillText("0°", 0, -PLOT_RADIUS - 5);

ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillText("90°", PLOT_RADIUS + 5, 0);

ctx.textAlign = "center";
ctx.textBaseline = "top";
ctx.fillText("180°", 0, PLOT_RADIUS + 5);

ctx.textAlign = "right";
ctx.textBaseline = "middle";
ctx.fillText("270°", -PLOT_RADIUS - 5, 0);

ctx.textAlign = "left";
ctx.textBaseline = "bottom";
for (let ele of [75, 60, 45, 30, 15]) {
	const r = elevationToRadius(ele);
	ctx.strokeStyle = "#dddddd";
	ctx.lineWidth = 2;
	ctx.beginPath();
	/*
	ctx.moveTo(r, 0);
	ctx.lineTo(r, 10);
	*/
	ctx.arc(0, 0, r, 0, Math.PI * 2);
	ctx.stroke();
	ctx.fillText(ele, 2, -r-2);
}

const gps = new GPS();

function elevationToRadius(e) {
	return PLOT_RADIUS * (1 - e / 90);
}

function writeToPng() {
	const out = fs.createWriteStream("test.png");
	const stream = canvas.createPNGStream();
	stream.pipe(out);
	out.on('finish', () =>  console.log('The PNG file was created.'));
}

//const prns = new Set();
let count = 0;
gps.on('data', function(parsed) {
	if (parsed.type !== "GSV") return;
	for (let sat of parsed.satellites) {
		if (sat.snr === null) continue;
		// prns.add(sat.prn);
		// console.log(Array.from(prns.keys()));
		const hue = (1 - (sat.snr / 40)) * 240;
		const x = Math.cos(sat.azimuth / 180 * Math.PI - Math.PI / 2) * elevationToRadius(sat.elevation);
		const y = Math.sin(sat.azimuth / 180 * Math.PI - Math.PI / 2) * elevationToRadius(sat.elevation);

		ctx.fillStyle = `hsla(${hue}, 100%, 50%, 0.5)`;
		// ctx.fillRect(x-1, y-1, 3, 3);
		ctx.beginPath();
		ctx.arc(x, y, 3, 0, 2 * Math.PI);
		ctx.fill();

		if (count % 100000 === 0) {
			writeToPng();
		}

		count++;
	}
});


const stream = fs.createReadStream("./log.log", "utf8");

const reader = readline.createInterface({ input: stream });
reader.on("line", (data) => {
	const nmea = data.substring("[2020-01-09T09:33:05.622] [DEBUG] default - ".length);
	if (/^\$GPGSV/.test(nmea)) {
		gps.update(nmea);
	}
});
reader.on("close", () => {
	console.log('done');
	writeToPng();
});
  1. トップ
  2. tech
  3. NMEA ログから衛星のスカイビューを生成する