MIDI デバイスを Lightroom のコントローラとして使えるプラグインとして MIDI2LR というのがある。市販のMIDIコントローラを使ってLightroomの現像パラメータなどを可変できるというもの。

しかし意外と市販のMIDIコントローラでちょうどいいのがないという問題がある。インターフェイス上の制限から、最適なコントローラは単純なロータリーエンコーダとスイッチの組合せで、常にインクリメンタルな値を送るものとなる。

ちょうどいいのがないなら専用品を作れば良いので、まずブレッドボードレベルで実装してみる。

コード

ロータリーエンコーダを処理するコードがあるのでちょっと長く見えるが main は短い。mbed の USBMIDI ライブラリのおかげ。

LPC11U35 で動かすことを前提として、ロータリーエンコーダのA相・B相を、それぞれ P0_11, P0_12 に繋ぐコードになっている。時計周りのときB相が先行するコードなので、逆のロータリーエンコーダの場合は逆にする。(ロータリーエンコーダの位相方向って決まってないっぽい?)

ロータリーエンコーダのチャタリング対策も入れてる。ググってみるとあんまりやってるコードを見ないけど必要ないのかな?

#include "mbed.h"
#include "config.h"
#include "USBMIDI.h"

class QEI {
	static const int8_t INVALID = 2;
	static uint8_t decode(const uint8_t prev, const uint8_t curr) {
		/**
		 * 4bit decode table
		 *       bit3      bit2      bit1     bit0
		 * [ prev A ][ prev B ][ curr A ][ curr B]
		 *
		 */
		switch ( (prev << 2) | curr) {
			case 0b0000: return  0;
			case 0b0001: return  1;
			case 0b0010: return -1;
			case 0b0011: return  INVALID;
			case 0b0100: return -1;
			case 0b0101: return  0;
			case 0b0110: return  INVALID;
			case 0b0111: return  1;
			case 0b1000: return  1;
			case 0b1001: return  INVALID;
			case 0b1010: return  0;
			case 0b1011: return -1;
			case 0b1100: return  INVALID;
			case 0b1101: return -1;
			case 0b1110: return  1;
			case 0b1111: return  0;
		}
		return INVALID;
	}

	volatile uint8_t prev;
	Timeout timeout;

	void interruptSample() {
		uint8_t ok = sample();
		if (!ok) error = 1;
	}
	
	void interruptDelay() {
		// avoid chattering
		timeout.attach(callback(this, &QEI::interruptSample), delay);
	}
public:
	volatile int position;
	volatile uint8_t error;
	InterruptIn phaseA;
	InterruptIn phaseB;
	float delay;

	QEI(PinName _a, PinName _b, float _delay = 0.005) :
		prev(0),
		position(0),
		error(0),
		phaseA(_a),
		phaseB(_b),
		delay(_delay)
	{
		phaseA.mode(PullUp);
		phaseB.mode(PullUp);
	}

	void enableInterrupt() {
		phaseA.rise(callback(this, &QEI::interruptDelay));
		phaseA.fall(callback(this, &QEI::interruptDelay));
		phaseB.rise(callback(this, &QEI::interruptDelay));
		phaseB.fall(callback(this, &QEI::interruptDelay));
	}

	/**
	 * sample digital input and return ok
	 */
	uint8_t sample() {
		uint8_t curr = phaseA.read() << 1 | phaseB.read();
		int8_t incr = decode(prev, curr);
		prev = curr;

		if (incr == INVALID) {
			return 0;
		}

		position += incr;

		return 1;
	}
};

void show_message(MIDIMessage msg) {
	switch (msg.type()) {
		case MIDIMessage::NoteOnType:
			printf("NoteOn key:%d, velocity: %d, channel: %d\r\n", msg.key(), msg.velocity(), msg.channel());
			break;
		case MIDIMessage::NoteOffType:
			printf("NoteOff key:%d, velocity: %d, channel: %d\r\n", msg.key(), msg.velocity(), msg.channel());
			break;
		case MIDIMessage::ControlChangeType:
			printf("ControlChange controller: %d, data: %d\r\n", msg.controller(), msg.value());
			break;
		case MIDIMessage::PitchWheelType:
			printf("PitchWheel channel: %d, pitch: %d\r\n", msg.channel(), msg.pitch());
			break;
		default:
			printf("Another message\r\n");
	}
}


USBMIDI midi;
QEI encoder1(P0_11, P0_12);

int main() {
	printf("init\r\n");
	encoder1.enableInterrupt();

	midi.attach(show_message);
	while (1) {
		if (encoder1.position) {
			int8_t val = encoder1.position;
			printf("send CC 1 %d 0\r\n", val);
			midi.write(MIDIMessage::ControlChange(1, val, 0));
			encoder1.position = 0;
		}
	}
}

MIDI2LR の設定

MIDI2LR の設定画面を開いた状態でロータリーエンコーダを動かすと、以下のように表示されるので、ここでは Exposure (露出) に割り当てている。

さらにこのボタンの部分を右クリックして、Adject CC dialog を出し、CC Message Type を Two's complement に設定する。

備考

これですんなり動く。ロータリーエンコーダのA相・B相の仕様を確認しないと回転方向が逆になったりするのだけ注意が必要 (ソフトウェア側でなんとでもなるけど)

この調子でエンコーダーを増やしていけば実装上は良いことになる。しかしロータリーエンコーダ1つあたり2ピンのIOを使うので、実際は GPIO 拡張が必要になる気がする。

  1. トップ
  2. tech
  3. mbed USBMIDI で Lightroom 用の MIDI インターフェイスを作る

BLE Nano を使っていた自作キーボードだったが、Mac のアップデートとともにまた不安定になってしまい、使う気を失ってしまった (数時間ごとに再ペアリングが必要に)。直す気力もなくてしばらく放置していたが、そろそろ観念して USB 接続のキーボードに作りかえることとした。

作りかえるといっても、キーボードの部分は I2C GPIO のモジュールとして動くように作ってあるので、マイコンまわりを載せ変えて実装を書くだけになる。

ということで、タイトルの通り LPC11U35 を使ってキーボードを実装しなおした。

コード: https://github.com/cho45/keyboard-lpc11u35

mbed official の USBDevice

mbed official に存在するライブラリの USBDevice の中に USBKeyboard というのがある。USBHID を継承していて、レポートデスクリプタとかを予め設定してくれる便利クラスとなっている。これは LPC11U35 でもちゃんと動くので基本的にハマるようなところはない。

標準で便利なメソッドがいくつか定義されているが、実際にキーボードを作る場合はこれらは使わない。USBKeyboard に定義されているメソッドはデモ用と考えていいと思う。

普通に使う場合は、レポートデスクリプタ定義はそのまま使いつつ (特に変更する必要がないので)、USBHID#send() を直接呼んで HID_REPORT を自分で構成して送ることになる。

基本的なコード

#include "mbed.h"
#include "USBKeyboard.h"

#include "mcp23017.h"

#define REPORT_ID_KEYBOARD 1
#define REPORT_ID_VOLUME   3

USBKeyboard keyboard;
DigitalOut led(LED1);
DigitalIn key1(P0_4, PullUp);

int main() {
    HID_REPORT report;
    
    uint8_t modifier = 0;
    uint8_t usage = 0;

    report.data[0] = REPORT_ID_KEYBOARD;
    report.data[1] = modifier;
    report.data[2] = 0;
    report.data[3] = usage;
    report.data[4] = 0;
    report.data[5] = 0;
    report.data[6] = 0;
    report.data[7] = 0;
    report.data[8] = 0;
    report.length = 9;
   
    
    while(1) {
        bool isKey1Pressed = key1.read() == 0;
        if (isKey1Pressed) {
            if (report.data[3] != 0x04) {
                report.data[3] = 0x04 /* a */  ;
                keyboard.send(&report);
            }
        } else {
            if (report.data[3] != 0x00) {
                report.data[3] = 0x00;
                keyboard.send(&report);
            }
        }
        wait_ms(10);
    }
}

基本的にはこういう形です。USBKeyboard のメソッドではキーの「押しっぱなし」ができない実装なので、ちゃんとしたキーボードにするにはレポートを自分で管理する必要があります。実用的にはリポート内のキーの状態を管理するクラスが必要になるでしょう。

といっても、USBKeyboard をほとんど使わないようなら直接 USBHID を継承して MyUSBKeyboard を作ったほうがいい気がするので (レポート定義もいじれるようになるし)、実際はそうしています。

ハマりポイント

sleep() がうまくいかない

ハマることはないと書いたが、メインループで sleep() (Active Sleeep) を使ってデバイスを割り込み待ちにするコードを書いたところ、USBHID#send() が失敗するという状態になった。どうやらUSBの状態まで狂わすようだったのでビジーループに変えた。

なんでおかしくなるのか、実装を読んだりマニュアルを読んだりして調べてみてもよくわからない。USB のクロックは有効だし、USB まわりの電源も sleep で切れるようなものはないように思える。

割り込み用のピンのプルアップが弱い

内部プルアップ時の電流がスペックだと 50μA となっている。電源電圧 3.3V なら 66kΩ 相当のプルアップとなる。

I2C を 2kΩでプルアップ動かしていると、この内部プルアップのピンをかなり動かしてしまうようだった。とりあえずは大丈夫そうだったが、今後誤動作の原因となりそうだったので、こちらも同様に 2kΩ で外部プルアップとした。

その他くだらないハマり

  • 一部のLANケーブルと相性がなぜか悪い
  • キーボード側で断線
    • かなり細いパターンの部分が見えないレベルで断線しており、割込みがかからない状態であった
  • sleep をやめたことによるバグ
    • キー入力がないときは I2C バスをやすませるような動作にしていたが、条件判定用の数値が sleep をやめたことによりアンダーフローしていた。
  • 時々キーが二重入力される
    • USB の通信遅延?
    • DEBUG 用に printf してるのが同期出力なのが原因っぽいので、本番で使う場合は必ず全 printf をオフにするように

接続

キーボード左右+USBコントローラという構成になった。USBコントローラとキーボードの左右はLANケーブルで接続する。BLE 版だとコントローラー基板はキーボードの左に付属していて、左右のキーボード同士をLANケーブルで接続していたが、実際はこのように配線を変えても動くような構成にしていたので、回路自体はすんなり変更できた。

電池がなくなったり、コントローラ基板を別にしたことで、キーボード全体の座高を低く、かつフラットにすることができた。今まで地味にキー位置が高くて、キーボード前にクッションが必要だったんだけど、その必要がなくなった。

筐体の作りなおし

3Dプリンタを得たのでより剛性の高い筐体になった。

  1. トップ
  2. tech
  3. LPC11U35 でUSBキーボードを作る

NKRO (N-key Rollover) というのは、おおざっぱに言うとキーボードのキーを同時押ししたとき、いくつ認識するか?ということです。

NKRO の理想的には以下の挙動になります。

  • いくつキーを押しても全て同時に押されていることがコンピュータから認識される

これを NKRO として定義すると、全ての USB HID キーボードは NKRO ではありません。なぜなら、USB HID キーボードは修飾キーを除くと6キーまでしか押されていることを送信できないからです。

USB HID での NKRO

そうすると妥協した次点として以下のような仕様を NKRO とするほかありません。

  • 6キーまでは同時押しがコンピュータから認識される
  • 7キー目を押すと、最初に押したキーが離されたと認識される

完全な同時押しは6キーまでですが、入力をとりこぼすということはありません。USB HID で NKRO と呼ばれているものはおそらくこの挙動をすると思います。

まぁ実際のユースケースとして、そもそも7キー以上の同時押しは極めて稀です。ということでさらに妥協して以下のような実装も考えられます

  • 6キーまでは同時押しがコンピュータから認識される
  • 7キー目を押しても無視される

これはこれでほとんど問題ないでしょう。単純に 6KRO になります。押した順番という状態を持つ必要が減るのでファームウェアの実装は簡単になります (バグを少なくできます)。一方で、キーを離さないクセを持つ人がものすごい高速タイピングをした場合にはキーをとりこぼすかもしれません。

PS-2 キーボードではどうなのか?

PS-2 キーボードのプロトコルは単純で、シリアル通信で

  • キーを押すと Make 信号を発生させる
  • キーを離すと Break 信号を発生させる

というものです。つまり NKRO になるかはOS側の実装次第です。

ゲーマー向けのマザーボードには PS-2 端子がついていることが多いですが、これは

  • NKRO 対応のため
  • レイテンシ削減のため

であると思われます。USB はホストからのポーリングで成りたっているため、キーを押した瞬間にコンピュータにデータが送られてくるわけではなく、コンピュータ側からのポーリングを待つ必要があります。ポーリング間隔はデバイス側から通知され、最小で1msまで設定可能ですが、OS 側に最終決定権があり、例えば Windows では最小で 8ms (Low Speed) , 1ms (Full Speed), 0.125ms (High Speed) です。つまりこの時点で最大で0.125msの遅延が発生します。

PS-2 の場合、キーが押された瞬間に Make 信号を送るため、理論的にはこちらのほうが早いことになります。

  1. トップ
  2. tech
  3. USB キーボードでの NKRO

(画像は過去に入力したデータを全て Google Fit へ入力しなおした様子)

Fit API 全体の概念

単純にグローバルな「体重」に対して値を追加するみたいになっているわけではない。

各アプリケーション(サードパーティ含む)は自分用の「データソース」を作る。これはセンサーに対応する。例えば「体重計 HOGE-001」みたいなデータソース。このとき「体重 (com.google.weight)」とかデータの種類と「浮動小数点」とかデータ型の定義をしておく。

データソースの定義に従って、データソースにデータポイントを追加してく。例えば「体重は 69.95kg」みたいな。

こうしていくと、複数のデータソースから「体重」データがいくつもできることになる。

実は Google Fit の画面から体重を入力すると user_input というデフォルトで存在するデータソースにそのデータは蓄積される。一方で、自分で独自の「体重」のデータソースを作って追記することもできる。これによって、データソースごとに自分のデータにだけ責任を持つという形にすることができる。

これらの「体重」のデータは最終的に derived:com.google.weight:com.google.android.gms:merge_weight というデータソースに集計されて、Fit で表示されている。

あとアクティビティ(ランニング)に対応するセッションとかがあるけど、今回は使ってないので調べてない。

「体重」を記録する場合

なんらかの方法で体重情報を取得できるとして、それを Google Fit に保存したい場合を想定する。

全体の流れは以下の通り

  • Fitness API が有効な OAuth の設定をする
    • Developer Console で 「Fitness API」を有効にしたプロジェクトをつくり、「OAuth 2.0 クライアント ID」を作成しておく。
  • 対応するデータソースを作る (体重計を1つのセンサーとみなして)
  • データソースへ値を追記する

Perl での例をしめす。CLI アプリケーションとしての実装。oob でキーを入力するため初回のみインタラクティブな インターフェイスになっている。

use v5.12;
use LWP::Authen::OAuth2;
use Path::Class;
use JSON;
use HTTP::Request::Common qw(GET HEAD POST DELETE PATCH);
use DateTime;

use constant {
	CLIENT_ID => '',
	CLIENT_SECRET => '',
};


my $token_file = file('.token_file');
my $token_string = eval { $token_file->slurp } || '';

my $google =  LWP::Authen::OAuth2->new(
	client_id => CLIENT_ID(),
	client_secret => CLIENT_SECRET,
	service_provider => "Google",
	redirect_uri => "urn:ietf:wg:oauth:2.0:oob",

	save_tokens => sub {
		my ($token_string) = @_;
		my $fh = $token_file->openw;
		print $fh $token_string;
		close $fh;
	},
	save_tokens_args => [],
	token_string => $token_string,
);

unless ($google->token_string) {
	# 新規 OAuth 認証
	my $uri = $google->authorization_url(scope => join(' ',
		'https://www.googleapis.com/auth/fitness.body.read',
		'https://www.googleapis.com/auth/fitness.body.write',
	));
	printf "Access to authorization: %s\n", $uri;
	printf "Input authorization code: ";
	my $code = <>;
	chomp $code;
	$google->request_tokens(code => $code);
}

# データソース作成
# 既にある場合は 409 になる
my $res = $google->request(POST "https://www.googleapis.com/fitness/v1/users/me/dataSources", 
	Content_Type => "application/json;encoding=utf-8",
	Content => encode_json({
		"application" => {
			"name" => "foobar baz",
			"detailsUrl" => "http://example.com",
			"version" => "1",
		},
		"dataType" => {
			"name" => "com.google.weight",
			"field" => [
				{
					"name" => "weight",
					"format" => "floatPoint"
				}
			]
		},
		"dataStreamName" => "foobar",
		"type" => "raw",
		"device" => {
			"manufacturer" => "my",
			"model" => "foobar",
			"type" => "scale",
			"uid" => "1000001",
			"version" => "1.0"
		}
	})
);

# 409 の場合エラーメッセージをパースしてデータソースIDを取得している
my $datasourceid = undef;
if ($res->code == 409) {
	my $json = decode_json($res->decoded_content);
	($datasourceid) = ($json->{error}->{message} =~ /Data Source: ([^ ]+) already exists/);
} elsif ($res->code == 200) {
	my $json = decode_json($res->decoded_content);
	$datasourceid = $json->{dataStreamId};
} else {
	die "failed to request creating data source";
}

unless ($datasourceid) {
	die "cannnot retrieve or create datasource";
}


# 送信するデータポイント
my $data_points = [
	{ epoch => ..., weight => 69.4 },
	{ epoch => ..., weight => 69.4 },
	{ epoch => ..., weight => 69.4 },
];

my $minstarttime = min map { $_->{epoch} } @$data_points;
my $maxendtime = max map { $_->{epoch} } @$data_points;

# 追加するリクエストは PATCH
# https://developers.google.com/fit/rest/v1/reference/users/dataSources/datasets/patch
my $datasetid = sprintf("%s-%s", $minstarttime, $maxendtime);
my $res = $google->request(PATCH sprintf("https://www.googleapis.com/fitness/v1/users/me/dataSources/%s/datasets/%s", $datasourceid, $datasetid),
	Content_Type => 'application/json;encoding=utf-8',
	Content => encode_json({
		"dataSourceId" => $datasourceid,
		"minStartTimeNs" => $minstarttime * 1000 * 1000 * 1000,
		"maxEndTimeNs" => $maxendtime * 1000 * 1000 * 1000,
		"point" => [
			map {
				{
					"dataTypeName" => "com.google.weight",
					"originDataSourceId" => "",
					"startTimeNanos" => $_->{epoch} * 1000 * 1000 * 1000,
					"endTimeNanos" => $_->{epoch} * 1000 * 1000 * 1000,
					"value" => [
						{
							"fpVal" => $_->{weight},
						}
					]
				}
			} @$data_points
		]
	})
);
say $res->as_string;

データソースを削除するには

既存のデータポイントが残っていると削除できないため、以下の手順を踏む

  • GET dataPointChanges で全てのデータポイントを洗って、startTimeNanos の最小値、endTimeNanos の最大値をもとめる
  • DELETE datasets で 求めた startime-endtime を datasetid とする (既存データポイントを削除)
  • DELETE dataSources を行う

ただ、データポイントを削除しても deletedDataPoint に入るだけで、完全に消えるわけではない。データソースも、削除は通っても、再度作成を行うと、deletedDataPoint が含まれた古いデータが復活する。ここらへんの挙動はよくわからない。

コード例は以下の通り

my $page_token = "";
my $minstarttime = "inf";
my $maxendtime = 0;

# データポイントを走査してデータ範囲を確定させる
while (1) {
	infof("GET dataPointChanges with token %s", $page_token);
	my $res = $google->request(GET sprintf("https://www.googleapis.com/fitness/v1/users/me/dataSources/%s/dataPointChanges?%s", $datasourceid, $page_token));
	$res->code == 200 or die "failed to get dataPointChanges";
	my $json = decode_json($res->decoded_content);
	use Data::Dumper;
	warn Dumper $json ;
	@{ $json->{insertedDataPoint} } or last;
	
	$minstarttime = min $minstarttime, map {
		$_->{startTimeNanos}
	} @{ $json->{insertedDataPoint} };
	$maxendtime = max $maxendtime, map {
		$_->{endTimeNanos}
	} @{ $json->{insertedDataPoint} };

	$page_token = "pageToken=" . $json->{nextPageToken};
}

# 全範囲のデータポイントを削除する
if ($maxendtime) {
	my $datasetid = sprintf("%s-%s", $minstarttime, $maxendtime);
	infof("Deleting existing data points for this data source %s", $datasetid);
	my $res = $google->request(DELETE sprintf("https://www.googleapis.com/fitness/v1/users/me/dataSources/%s/datasets/%s", $datasourceid, $datasetid));
	say $res->as_string;
} else {
	infof("There are no data point");
}

# データソースを削除する
infof("Deleting this datasource");
my $res = $google->request(DELETE sprintf("https://www.googleapis.com/fitness/v1/users/me/dataSources/%s", $datasourceid));
say $res->as_string;

OMRON の Wi-FI 体重計

突然話は変わるがOMRON の Wi-FI 体重計を買ったのは失敗だなーと思っている。Bluetooth 体重計のほうがハックしやすいと思うからだ。Wi-Fi 経由で https でサービスと接続されているとサービス側の仕様変更やサービス終了の影響をうけてしまう。そして実際、オムロンはPC側のサービスを終了してしまった。

しかし BT 対応の体重計を買いなおすのも嫌なので、Android アプリが取得しているデータを普通にスクリプト (Perl) で取得できるようにして、Fit にインポートできるようにした。毎日動かせば常に Google Fit 側へデータが同期されるので、たとえ OMRON のサービスが終了しても、最悪データは失われない。リバースエンジニアリングしたので同期スクリプトの公開は控えるが、Google Fit のノウハウだけ記録しておく次第 (BT 体重計から Fit へ同期するアプリケーションなんかを書くときに役立つはずだ)。

Google はウェブの会社で、ユーザーデータの重要性はよくよく理解していると思われるので、サービス終了の際にエクスポートをちゃんと提供することが期待できる。一方でオムロンにそれは期待することはできない。PC版の閲覧サービス終了させてきたしね。

  1. トップ
  2. tech
  3. Google Fit の REST API で体重を自動入力する

自作キーボードのコネクタとして 8P8C を使っていて、市販のLANケーブルを流用しているのだけれど、特定のLANケーブルで動作せず悩んだ。

結局は表題の通り、8本のうち4本しか結線されていないLANケーブルだったのが原因だった。

こういうケーブルは「カテゴリー5相当」と言う類のものらしい。

カテゴリー6の時代に今更こんなケーブルは売ってないと思うが、古いLANケーブルには注意しましょう。普通にイーサネット的にはリンクアップはするので罠いです。

コネクタ流用でこんな罠があるとは思わなかった。

  1. トップ
  2. tech
  3. 4本しか結線されてないLANケーブルでハマった

どんな分野でもまず「温度感」をつかむのが大事だと思っていて、そもそも「温度感」って何かというと、どの部分の神経を使うか、熱量を使って考えて、どこの手を抜くかという話です。「コツ」を少し具体的に表現して「温度感」といっている、というとちょっと身も蓋もないのですが。

3Dプリンタのモデル設計の温度感

ここでいう 3D プリンタは普及している FDM (Fused Deposition Modeling・熱溶解積層法) の話です。FDM は基本的に X/Y 精度が悪く、Z精度はそれに比べると高いという特徴があります。

具体的には X/Y については±0.5mmぐらいで、Zは±0.1mmで考えます。特に X/Y の精度の悪さは設計難易度にかかってきます。同一条件で出力したパーツを組合せる分にはともかく、他の規格品、例えばナットや、あるいは三脚ネジのようなものを出したいときは、だいたいの場合は何度かプリントして現物あわせをする必要があるのです。

±0.5mm これはキャリブレーションしてフィラメント量を調節していてもなお生じる誤差と考えたほうが良くて、キャリブレーションしていなければもっと精度が出ません。

動くすきまを確保したい場合

基本的に動く面すべてに 0.5mm のトレランスをもうけます。つまり引き出しみたいなものなら、1mm 小さい立体を入れるようにします。

ぴったり圧入する場合

パズルのピースのようにハメこむ場合は、0.25mmのトレランスをもうけます。四角に四角をハメる場合 0.5mm 小さい立体になります。しかし実際には素直にうまくいくことは少ないでしょう。絶対に失敗しない方法は 0.2mm 程度のトレランスで、出力してから削ることです。

圧入して固定するような場合、これはかなり難しいですが、形状で工夫すると楽になります。設計上は、内接多角形の柱と同一内径の穴をモデリングして無理矢理ハメこむのが楽です。

  1. トップ
  2. tech
  3. 3Dプリンタで出力するモデル設計の温度感

しばらくすると忘れてしまうので、現状のフローをまとめておく。

  1. pcb2gcode の millproject
  2. AutoLeveller は基本的には常時使うほうが結果的に早い。DIP しか使わないような電源回路では使わなくてもなんとかなるけど使ったほうが安心。
  3. 基板外形のカッターは 1.5mm が適当。1mm だと 500mm/min でフィードするとちょいちょい折れてストレス
  4. パターン切削は30度0.1mmのカッター

概要

  • KiCAD からの出力
  • pcb2gcode の設定
  • machinekit (LinuxCNC) での操作

KiCAD でのデザインルール

0.25mm 幅で削るので、これが限界。0.05mm でもトレランスをとれれば安定しやすい。

  • 最小配線幅は 0.25mm が限界。できれば 0.3mm のほうが安定。
  • クリアランスも 0.25mm が限界。できれば 0.3mm ぐらいにしたい。

KiCAD からのエクスポート

- 切削する銅箔層 (F.Cu or B.Cu) と Edge.Cut をガーバーに出力する
- ドリルファイルを出力する

pcb2gcode

以下のような前提で gcode 化する

  • 30° 先端0.1mm のVカッターを掘る用に使う
  • φ0.8mm のエンドミルを全ての穴開けに使う
  • φ1.5mm のエンドミルかφ1mmで2枚刃を外形カットに使う
  • 基板の厚さは 0.8mm か 1.6mm を使う。基板の歪みを矯正できるぶん、0.8mm のほうが細かいパターンは削りやすく感じる。

先端0.1mmのエンドミルといっても、実際買ってみると綺麗に 0.1mm になっていることはすくない。というかすぐ先端が折れる。計算するときは 0.15〜0.20mm として扱う。

切削速度を落とせばφ0.8mmで穴開け〜外形カットまで可能で、ツール取り替えの手間が減らせるはずだけど、0.8mm のミルは高価であんまり折りたくないので、別途φ1.5mmのミルを使っている。これは今後 0.8mm にするかも。あんまり太いミルで外形カットすると切削抵抗が大きくなり、両面テープが剥れてずれる可能性もあるので注意。

# Use standard mm
metric=true
metricoutput=1

front=tachometer-B_Cu.gbr
#back=
outline=tachometer-Edge_Cuts.gbr
drill=tachometer.drl

front-output=front.gcode
back-output=back.gcode
outline-output=outline.gcode
drill-output=drill.gcode
milldrill-output=milldrill.gcode

# https://github.com/chrysn-pull-requests/pcb2gcode/blob/graphical-documentation/man/options.svg
zwork=-0.1
zsafe=1
mill-feed=220
mill-speed=10000
mill-vertfeed=150
mill-diameters=0.22
milling-overlap=25%
isolation-width=0.5

zdrill=-1.7
zmilldrill=-1.7
zchange=20
drill-feed=100
drill-speed=10000
min-milldrill-hole-diameter=0
milldrill-diameter=0.8

# 外形カット時のミル直径
cutter-diameter=1
# PCB板厚+0.1mm
zcut=-1.7
cut-feed=500
cut-speed=10000
cut-infeed=1
cut-side=front
# mirror-axis=51.575mm

optimise=true
zero-start=true

pcb2gcode 2.0.0 で動くように変更

pcb2gcode して生成 gcode をデスクトップとリモートの Machinekit に転送する。デスクトップにコピーしているのは CAMotics で読みこませて確認するときに便利なので。

  • mirror-axis を基板サイズの半分に設定すること
    • これやらないと back 面がマイナスからはじまってしまう
    • 一度実行して表示される width を元に書きかえる必要がある
~/ghq/github.com/pcb2gcode/pcb2gcode/pcb2gcode && cp *.gcode ~/Desktop/ && scp *.gcode machinekit@192.168.0.240:gcode

確認

  • CAMotics を起動して、すべてのファイルを読みこむ。
  • Workpiece を以下のように設定して再描画
    • X: xxx/0
    • Y: xxx/0
    • Z: 1.6/-1.6

段取り

  • 事前に捨て板の面出しをする (φ6mm ぐらいのミルで加工面全てを 0.2mm ほどさらう)
    • AutoLeveller 使うなら毎回やる必要はない。
  • ニチバン ナイスタック 透明タイプ (材質がセロハンのもの) を使って切削基板を捨て板に固定する
  • このときしっかり全面を押さえつけて捨て板に接着する (加工時の圧力で高さが変わらないように)

AutoLeveller

  • AutoLeveller に切削対象 gcode を読みこませる
  • Probe Clearance は 0.5〜1、Z Safe Height は 1、Z feed は 50 にする
  • create probe file only にチェックを入れて、Create Levelled Gcode ボタンを押すと、ALProbeback.ngc ができる
  • ALProbeback.ngc を machinekit に読みこませる
  • 一旦 Probe を行い、Z を Touch Off する (原点を設定する)
  • 実行して基板全体を Probe する。
  • .ini と同じディレクトリに RawProbeLog.txt ができているので、ローカルに転送する
    • gcode 用のディレクトリに RawProbeLog.txt への symlink を貼っておくと便利
  • RawProbeLog.txt を AutoLeveller に読みこませる
  • create probe file only にチェックをはずして Create Levelled Gcode を押す
  • ALback.ngc ができるので転送する

drill や外形カットなどは面倒なので AutoLeveller はかけず、0.1mm 余計に掘る。

https://lowreal.net/2016/10/19/1

AutoLeveller の起動

java -cp ~/ghq/bitbucket.org/daedelus1982/autoleveller/out/artifacts/autoleveller_jar/autoleveller.jar  com.cncsoftwaretools.autoleveller.Autoleveller

生成 gcode の転送

scp ~/Desktop/*.ngc machinekit@192.168.0.240:gcode 

プローブログを手元に転送

scp machinekit@192.168.0.240:gcode/RawProbeLog.txt ~/Desktop/

切削手順

基板パターン

  • ALback.ngc を machinekit に読みこませる
  • 実行すると Probe をつけろと言われるので、つけて Resume
  • Probe後、Probe をはずせと言われるので、はずして Resume
  • 切削がはじまる

ドリル

  • エンドミルをφ0.8mmに交換
  • Probe を行い、Z Touch Off
  • Feed Override を 10% ぐらいに下げる (折れ対策。pcb2gcode が mill 時と milldrill 時でフィードレートが変えられないので)
  • drill.gcode を読みこんで実行

外形

  • エンドミルをφ1.5mmに交換
  • Probe を行い、Z Touch Off
  • outline.gcode を読みこんで実行

備考

pcb2gcode の --al 系オプションは使わないの?

ある程度規模が大きくなると machinekit に読みこませたときに非常に時間がかかってしまうので、その時間を使うぐらいなら AutoLeveller を使ったほうがよいと考える。時間がかかるのはプレビューのために gcode を一通り仮想的に実行しているためだと思う。

また、--al のオプションは Probe → 切削 がひとつの gcode で行われるため、リトライすることができない。途中でミルが折れたりすると交換してやりなおすということができなくなる。

エンドミルの不具合

基板切削をはじめた直後に削った銅がけばだつようだったらミル先端が既に折れている可能性がある。そのまますすめてもうまくいかないので新しいのに変えること

または確認しても折れていないようなら、ミルの突き出し量を見直すこと。ぎりぎりまでマシンに指しこんでいないと、同様にけばだつことがある。

  1. トップ
  2. tech
  3. KiCAD + Machinekit (LinuxCNC) で切削して基板をつくるときの手順

秋月でLPC11U35 が乗っているボードを買った。うっかり3枚買っていた。EA LPC11U35 QuickStart Board
と互換のもの。

RAM や Flash が少なめだけど以下の点で魅力的なボード

  • mbed 対応
  • 850円と安価
  • 単体で USB 経由で書きこめる
    • デバッグするならシリアルは別途繋ぐ必要あるけど
  • 別のチップに対して USB SWD インターフェイスになれる

コード的な備考を先に書いておく

  • LED1 〜 LED4 は全部 P0_7 のエイリアスになっている。
  • USBTX/USBRX はサポートされない

Lチカのコードは何の変哲もない。

#include "mbed.h"

DigitalOut led(LED1);
// Serial serial(UART_TX, UART_RX);

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

ビルドするも動かない

platformio でビルドしたのを書きこんでもさっぱり動かず、再度 CRP DISABLED というボリュームがマウントされてしまう。オンラインコンパイラでは動くので、手元の環境の問題であることはわかったが、なかなか原因がわからなかった。

結局 platformio は mbed OS 5 の環境でビルドしようとするが、LPC11U35 では RAM が足りず起動できないようだ。

実際、mbed の公式を見て mbed OS 5 系に対応するボードをリストにすると (そういうことができることにはじめて気付いたが)、LPC11U35 は出てこない。

platformio でフレームワークのバージョンを指定してビルドする方法がどう調べてもわからなかった。

mbed cli を使う

そういうことで、いろいろ試したけど、あきらめて platformio のことは忘れましょう。

ARM 公式で提供されている mbed-cli をいれるのが今のところは最良のようです。

インストール

まず Python のツールなので python が必要なのと、pip も必要です。ここでは

  • python は macOS のシステムに入っているものを使う
  • pip はグローバルに入れる
  • mbed-cli もグローバルに入れる
  • ほかだいたいの依存モジュールはユーザー領域にいれる (pip --user)
    • 既存の setuptools をアップグレードしようとするのだが OS 保護に守られて root でも上書きできないので、基本的に --user で入れたほうが良い。

という感じでいきます。

curl https://bootstrap.pypa.io/get-pip.py | sudo python
sudo pip install mbed-cli IntelHex

mbed-cli には GCC_ARM のツールチェーンを入れてくれる機能はなく、前もって arm-none-eabi-gcc とかを入れておく必要がある。https://launchpad.net/gcc-arm-embedded とかを入れてパスを通しておく。

もしくは、platformio で既に入れてあるのがある場合は以下のようにしてパスを通せば使える。

export PATH=$HOME/.platformio/packages/toolchain-gccarmnoneeabi/bin:$PATH

プロジェクト作成

今回使う LPC11U35 は mbed OS 5 未対応のため、mbed 2 (いろいろ別名がある。mbed library / mbed classic など) を使うようにプロジェクトを作る。--mbedlib を指定すると mbed 2 環境になる。

sketch はプロジェクト名。

mbed new sketch --mbedlib
cd sketch
vim main.cpp
mbed deploy
pip install --upgrade --ignore-installed --user -r .temp/tools/requirements.txt
mbed compile -t GCC_ARM -m LPC11U35_401
# LPC11U35 を USB かきこみモードにして
cp ./BUILD/LPC11U35_401/GCC_ARM/sketch2.bin /Volumes/CRP\ DISABLD/firmware.bin 
#リセットボタンで動く

しかし mbed-cli は凶悪で、プロジェクトディレクトリ以下に 2GB ぐらいファイルをコピーしてくる (mbed 2 の全ソースコード)。

自分は複数端末 (ノートとデスクトップ) で開発する関係で Dropbox のディレクトリで同期させておくことが多いのだが、これだと死ねる。つらい。

ピン名など

回路図

見比べると結構違う。

  • Q1 の違い。秋月版はFET
  • 3.3V レギュレータの違い
  • リセットICの違い
  • USB 入力の保護の違い

~/.platformio/packages/framework-mbed/targets/TARGET_NXP/TARGET_LPC11UXX/TARGET_LPC11U35_401 以下にある。

https://os.mbed.com/users/mbed_official/code/mbed-dev/file/57724642e740/targets/TARGET_NXP/TARGET_LPC11UXX/

  1. トップ
  2. tech
  3. LPC11U35 の mbed ローカル開発環境構築

LM1972 デジタルボリューム | tech - 氾濫原 の続きで、Arduino ではなくmbed環境での実装。

LPC11U35 で試した。AD1 にBカーブボリュームをつけてこれによって 78dBから0dbまでアッテネーションレベルをかえる。

#include "mbed.h"

// mosi miso sck
//SPI spi(P0_9, P0_8, P0_10);
//DigitalOut cs(P0_11);

class LM1972 {
    SPI spi;
    DigitalOut cs;
    const uint8_t daisy_chain;

public:
    static const uint8_t MUTE = 0xff;

    // uint16_t to 1dB step attenuation value
    static inline uint8_t volumeToAttenuation(const uint16_t v) {
        // volume 0%   -> 78dB (126)
        // volume 100% -> 0dB (0)
        const uint8_t dB =  ((uint32_t)(1023 - v) * 78 / 1023);
        uint8_t att = 0;
        if (dB < 48) {
            att = 2 * dB;
        } else {
            att = 96 + dB - 48;
        }
        return att;
    }

    static inline uint8_t volumeToAttenuation(const float v) {
        // volume 0%   -> 78dB (126)
        // volume 100% -> 0dB (0)
        const uint8_t dB =  78 * v;
        uint8_t att = 0;
        if (dB < 48) {
            att = 2 * dB;
        } else {
            att = 96 + dB - 48;
        }
        return att;
    }

    LM1972(
            PinName _mosi,
            PinName _miso,
            PinName _sck,
            PinName _cs,
            uint8_t _daisy_chain = 1
          ) :
        spi(_mosi, _miso, _sck),
        cs(_cs),
        daisy_chain(_daisy_chain)
    {
        spi.format(8, 0);
        spi.frequency(1e6);
        cs = 1;
    }

    inline void setAttenuation(const uint8_t channel, const uint8_t v) {
        cs = 0;
        // >150ns
        wait_us(2);
        spi.write(channel);
        spi.write(v);
        // >150ns
        wait_us(2);
        cs = 1;
    }

    uint8_t setVolume(const float v) {
        const uint8_t att = volumeToAttenuation(v);
        for (int i = 0; i < daisy_chain; i++) {
            setAttenuation(0, att);
            setAttenuation(1, att);
        }
        return att;
    }

    uint8_t setMute() {
        for (int i = 0; i < daisy_chain; i++) {
            setAttenuation(0, MUTE);
            setAttenuation(1, MUTE);
        }
        return MUTE;
    }
};

DigitalOut led(LED1);
Serial serial(UART_TX, UART_RX);
AnalogIn pot(P0_12); // AD1

LM1972 volume(P0_9, P0_8, P0_10, P0_11);

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

    volume.setMute();

    for (;;) {
        led = 1;
        wait(0.5);
        led = 0;
        wait(0.5);

        float val = pot.read();
        serial.printf("pot val = %d\r\n", (int)(val * 100));
        uint8_t att = volume.setVolume(val);
        serial.printf("set att = %x\r\n", att);
    }
}

多摩川河口付近にある旧穴守稲荷神社大鳥居 (大田区羽田空港) から、阿蘇神社鳥居 (東京都羽村市) までのサイクリングロードを往復で走った。116kmを休憩込みで5時間37分ぐらい。

少し前にほぼ同じことをしたんだけど、完全走破ではなかったので、もう一度チャンレンジした。この往復は目標のひとつだったのでひとまず達成できてよかった。

多摩川は今、各所で堤防強化の工事をやっていて、本来のルートを通れないことが多い。堤防上と河川敷をいったりきたりすることになる。

今の自分だとこのぐらい走ると限界。気温5℃ぐらいで寒くて寒くてつらかった。

4ch 分の音量を一括で変更したいと思ったが、4連ボリュームというのは入手性が極めて悪く、市販の2連ボリュームを改造して4連にしたりするようだ。

ということでそもそもアナログボリュームをやめてデジタルポテンションメータを使うことを検討しはじめた。デジタルボリュームならギャングエラーも少ないし、多チャンネル化もしやすい。インターフェイスも自由にできる (といってもアナログBカーブボリュームをADCして4ch分可変させる予定だが)

LM1972

秋月で売ってる中から選ぶと、実質的にはこれしかない。1つで2チャンネル分。これを使ってひとまず2ch分を作ってみる。

実装

回路図

後段のオペアンプは実際は手元にあった OPA2134 を使っている。単電源でも使えるが、今回は正負電源を用意する予定なのでVSSとGNDはわけてある。

基板

コード

さくっと書きこんで動かせるので Arduino Nano で実験。

  • ADC 0 から値を読んで、0から78dBの間を可変する (ミュートなし)
  • ADC 0 にBカーブ可変抵抗器をつける (0〜5V分圧可変)

という前提。以下のようなことに気をつける。

  • SPI は MODE 0。ローからはじまって立ちあがりで読む。
  • D10 SS を使ってるけど、マスターなので特にこのピンを使う意味はない
  • LOAD/SHIFT は1ch書きこむごとにオン/オフすること (16bit 書くごと)
  • DATA-OUT をデイジーチェーンして次の DATA-IN に繋げる場合は単に4ch分を連続して書きこむ

volumeToAttenuation() は 0〜1023 の値を 1dB ステップで 0〜78dB の設定値に変換する関数。48dB を境に減衰量が変わるので注意が必要。0.5dB ステップは使ってない。

#include <Arduino.h>
#include <SPI.h>

void setAttenuation(const uint8_t v) {
	// channel selection
	//     0x00 (channel 1) / 0x01 (channel 2)
	// attenuation setting
	//     0b00000000 (0) 0.0dB
	//     0b00000001 (1) 0.5dB
	//     0b00000010 (2) 1.0dB
	//     ...
	//     0b01100000 (96) 48.0dB
	//     0b01100001 (97) 49.0dB
	//     0b01100010 (98) 50.0dB
	//     ...
	//     0b01111110 (126) 78.0dB
	//     0b01111111 (127)  100.0dB (Mute)
	//     0b10000000 (128) 100.0dB (Mute)
	//     ...
	//     0b11111111 (255) 100.0dB (Mute)
	//
	// initial state is Mute
	digitalWrite(SS, LOW);
	delay(1);
	SPI.transfer(0x00);
	SPI.transfer(v);
	delay(1);
	digitalWrite(SS, HIGH);

	delay(1);

	digitalWrite(SS, LOW);
	delay(1);
	SPI.transfer(0x01);
	SPI.transfer(v);
	delay(1);
	digitalWrite(SS, HIGH);
}

uint8_t volumeToAttenuation(uint16_t v) {
	// volume 0%   -> 78dB (126)
	// volume 100% -> 0dB (0)
	uint8_t dB =  ((uint32_t)(1023 - v) * 78 / 1023);
	uint8_t att = 0;
	if (dB < 48) {
		att = 2 * dB;
	} else {
		att = 96 + dB - 48;
	}
	return att;
}

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

	// LM1972 Pin / Arduino Pin
	//  (9) CLOCK <-> (D13) SCL
	//  (10) LOAD/SHIFT <-> (D10) SS
	//  (11) DATA-IN <-> (D11) MOSI
	pinMode(SS, OUTPUT);
	digitalWrite(SS, HIGH);
	SPI.setBitOrder(MSBFIRST);
	SPI.setClockDivider(SPI_CLOCK_DIV16);
	SPI.setDataMode(SPI_MODE0);
	SPI.begin();
}


uint8_t prevLevel = 255;
void loop() {
	uint16_t val = analogRead(0);
	uint8_t att = volumeToAttenuation(val);
	if (abs(att - prevLevel) > 1) {
		Serial.print("analogRead = ");
		Serial.print(val);
		Serial.print(" att = ");
		Serial.println(att);
		prevLevel = att;
		setAttenuation(att);
	}
}

備考

最初うっかり±15V、つまりVSS=-15V, VDD=15V をかけてしまい、燃やしてしまった (煙が出て、はんだが溶けてチップが脱落した)。LM1972 は絶対最大定格がVDD-VSS=15V、Typical 12V なので、定格の倍以上の電圧をかけていたことになる。あたりまえだけど定格は確認しないといけない。設計段階で回路図を書く前に気付くべきな、あまりにもしょうもないミス。自戒のためアホなことをしたことも書いておく。

運用上でのよくない点も災いした。

いつもは電源をはじめていれるときは安定化電源を電流制限 (CC) をかけつつオンにし、電流量を見るのだが、今回は正負電源を用意する必要があったため、初回から直接手元にあったトランス経由の電源回路へ繋いでしまった。おかげで異常に電流が流れていることに気付きにくかった。

ということで、正負電源を用意しやすくし、事故を減らすために電源を1台増やす予定。


結局 ±5V で試した。当然うまくいった。

  1. トップ
  2. tech
  3. LM1972 デジタルボリューム

3歳児検診のとき、自宅で視力検査とかをする。普通の検査と同様に、ランドルト環の方向を本人に示してもらうようなもの。しかし真面目に書いてある通りにやったら (練習→本番とやるのだが、本番はやりなおし不可)、ちゃんと答えてくれず、結果をそのまま提出したらひっかかってしまって、赤紙 (2月n日に検査するのできてください、という実際に赤い紙) がきた。

結果的には何の問題もなかった。一通り

  • 眼の写真? (両目を何らかの機械で撮影して数値を出してた)
  • ホログラム?なんていうかわからんけど、飛びだすカードで立体視の検査
    • ランダムに見えるカードだけど裸眼立体視できるようになってるやつ
    • 3つのオブジェクトが隠れているので、見つけてね、みたいな感じ
  • ライトを上下左右に追わせて眼球に動きをみる (斜視の検査?)
  • ランドルト環で視力をはかる
    • 「右」とか「左」とかは難しいので、ランドルト環の模型を持たせて、同じようにあわせる

をやった。

LM1972 デジタルボリューム | tech - 氾濫原 に書いたが、正負電源を簡単に用意できない環境だったのが一因で事故が起きたので、もう一台安定化電源を買った。(写真右が買ったもの)

メトロニクスの524Bという型番のものを買った。会社そのものが既にないので、いつ販売されていたモデルかすらわからない。少なくとも自分が小学生のころにはどっかの企業で償却されたものが手元にあったので30年ぐらい前のものではないかと思う。

なんとなく既存のものと同じのを並べたかったので、中古で売ってるのを探して買ってみた。送料もろもろ込みで5000円ぐらい。新品でもっと出力とれるものもあるけど、好みの問題です。インターフェイスがかっこいいんだよね。

チャイルドトレーラーを買ってみた。Burley Bee というもの。

なぜこれが必要か

ベビーカーを卒業して3ヶ月ほど経過したが、こどもがインフルエンザになったときに、病院まで連れていく方法があまりなくて困ったのがきっかけ。子どもも3歳になり体重も15kgぐらいになっている。

タクシーを使えばいいんだけど (実際、休日診療所は遠いのでタクシーをつかった)、かかり付けの小児科は距離が微妙で、タクシーを待っている時間に着いてしまうぐらいの近さだけど、一方で子どもを担いで行くには若干坂もあって遠い。

このときは親戚に子ども乗せ自転車 (子乗せママチャリ) を借りて小児科までいったが、これがだいぶ怖かったうえに疲れた。子どもを乗せると重くて漕ぎ出しでフラつくし、スタンドの上げ下げ時にコけそうになるしでめちゃくちゃ怖くて、二度とのりたくないと思った。安全第一で考えれば、子乗せママチャリなんてイカれた設計は許されるものではないと思った。

しかし子乗せママチャリに3人乗りで爆走してる人とかいるけど、怖くないんだろうか……

チャイルドトレーラー

他に選択肢はないか、と考えてみるとチャイルドトレーラーが浮かんだ。

 -

4.0 / 5.0

チャイルドトレーラーは既存の自転車のリアハブにアタッチメントをつけて牽引するタイプ。トレーラー側にタイヤがついており、静止時の重量はそっちのタイヤで支えている。

牽引する自転車が転倒しても、子どもが乗るトレーラーには影響がない。重心が低いので乗せたり降ろしたりするときに恐怖を感じることはない。というか3点支持で原理的に安定している。牽引するほうの自転車は重くならないので、ふらついたりすることはなく、普通に乗るのとバランスのとりかたは全く変わらない。

おおむね7歳ぐらいまでを想定したつくりらしい。トレーラー内に2人乗り (シートベルトが2つある) 。重さは45kgまでだけど、天井があるため1人乗りでも座高で制限をうけると思われる。

Burley Bee は本体重量 9kg。Honey Bee などグレード違いでベビーカーにもなるタイプのトレーラーもある。ただ、デカいのでベビーカーモードでも普通のベビーカーのように電車に乗ったりはできないと思ったほうがいい。徒歩で小児科いくときに使いたいとかならベビーカーにできたほうが楽かもしれないと思った。

牽引する感覚

前提としてクロスバイクでひいている。ママチャリでもひけるけど、坂のこととブレーキのことを考えると軽いギアが多くて制動力が高い (Vブレーキやディスクブレーキ) クロスバイクなどか、電動アシスト自転車 (車重が重いのに速度も出せるのでブレーキ制動力も高めに設計されている) が良さそうと思った。

漕ぎ出しが少しだけ重くなるが、ひと漕ぎして慣性がつけば、転がってくれるのでほとんど牽引している感覚はなくなる。本当についてきてるか?と不安になるぐらい。上りは相応に重くなるはずだが、早く走ろうと思わないのでギアを落とせば意外と気にならない。立ち漕ぎでバイク振るのも特に違和感なくできる。下りで押されるような感覚もなかった。

若干車体が左に寄るような設計になっているので、あまり車線の左側ぎりぎりを走ると電信柱や縁石にひっかかりそうで怖い。左側にミラーが欲しいと初めて思った。

トレーラーの最大のデメリットは法律

牽引すると「普通自転車」でなくなり、普通自転車で可能ないくつかの行為ができなくなる。

  • 歩道通行禁止
  • 「自転車を除く」の交通標識に従う必要がある
    • 一方通行や進入禁止で困ることがある
    • 交通標識における「自転車」は「普通自転車」のことなので、牽引した自転車は該当しなくなる
  • 交通のひんぱんな道路の通行禁止
  • サイクリングロードが走れない
    • 普通自転車専用通行帯は走れるらしい (謎)

厳密に守るとかなり難しいときがある。

例えば高架の自動車専用道路 (だいたい交通がひんぱんな道路) へ誘導され、平行する道路が「進入禁止 (自転車を除く) 」になっているような道路だと、どちらも通れないので困る。ややこしいマイナールートを探しだす必要がある。

平行する道路がとぎれて幹線道路に合流するような場合も困る。

橋みたいな必然的に交通が集中するところでも乗って通行することができない。

つまり普通自転車でも自動車でも困らない思わぬ行き止まりが頻発する。ユーザーが少ないせいなのでもっと流行ればいいと思う。


本当にどうしようもない場合、降りて歩くぶんには歩行者扱いとなるので、降りて歩道という手がある (乗って徐行はできないが降りるのは問題ないらしい)。

ということで初めて通るルートの場合、事前に自転車単体で走行して下見したほうが確実。ストリートビューでおおまかにルートを確認して、一回自転車単体で実走してみるぐらいの段取りをしたほうが良い。


速度も目安は直線で最大25km/h、曲がるときは8km/h以下。

自転車乗るとき腰が痛くなりがちだったので、プランクとスーパーマンみたいなやつと腹筋ローラーを多少やってる。腰に効果ある気がしてるけど、それよりもとにかくお腹が緩くなる。毎日うんこでるのはいいけど、通りこして夜中に下痢で起きたりする。

保育園からの要請でミシンした。型紙をレンタルするから作ってこい、みたいなのりなんだけど、完全なレシピは用意してくれないので、最大限忖度して作らないといけなかった。というか時間なくて保育園に預けてるんだから作らなくても買えるようにしてほしい。

まぁそれはともかく、仕様が雑なので外注するのも難しいわけで、結局作ったほうが早い。

「エプロン」と書いてあったけど、構造的にはノースリーブワンピースのようなもので、袖口とかが存在する。「服」は小物と違って曲線で立体的なので素人には難しい。

型紙から 10mm 縫いしろをつけて裁断し、完全に同様の例はないものの既存の本を参考にしながら以下の手順で完成とした。

  • 前身頃・後ろ身頃をそれぞれ裁断
  • 周囲をジグザグですべてかがる
  • 表あわせで前身頃・後ろ身頃を縫いあわせる
  • 袖口・襟などを内側に 10mm 折ってステッチ
  • うらがえす
  • 袖口と襟をパイピング
  • 後ろにゴムをつける (つけかたが分からなかったので適当にミシンで2往復した)

布団カバーを作ったときに余っていた布を使ったので生地が若干うすい。あとやたらシックな感じになった。

同じ布・バイアステープで給食帽もつくった (写真はない)。こちらのページを参考に直径40cmで、バイアステープでゴムを入れる部分をつくった。両折りではなく縁取り用のバイアステープを使ったので広げながら縫ったけどなんとかなった。

ググった

「前身頃」「後ろ身頃」

この用語を初めて聞いたのでググった。それぞれ服の前のパーツ・後のパーツの意味らしい。

バイアステープの処理

バイアステープで縁取り(パイピング)するのが初めてだったので、「バイアステープ 処理」「バイアステープ 縫い終わり」とかでググった。買ったのは縁取り用に折られているバイアステープ 11mm。

やらなくても直接的に服の機能にあまり問題はないけど、袖や首まわりは肌にあたる可能性があり、かがった部分が直接露出しているとあんまりよくなさそう。またカーブしている部分は縫いしろに切り込みを入れたりするので、本体だけでは綺麗に処理できず、パイピングすると相当綺麗に仕上るように見える。あと袖とかはバイアステープで処理すると形が安定する模様。

例によって本とかにも「何のためにパイピングするか」ということは書いてないことが多いので、何らかの目的を持ってこれを行うのだということが書いてあるものが欲しくなる。

  1. トップ
  2. tech
  3. 最近のミシン - エプロン

実態が似たようなものなので紛らわしいが動作に違いがある。

ラジアルは放射状の設置されたエレメントが等電位面を形成する。この等電位面は大地とは別のもの。

カウンターポイズは大地との間でコンデンサを形成することで、交流的に大地と接続する。つまり大地そのものを等電位面として利用する。

動作の境目

十分に大地と容量結合できている場合はカウンターポイズとしてはたらく。低い周波数ほど容量を増やさなければいけない。容量結合できればいいので放射状に置く必要はない。

容量結合が十分ではない場合はラジアルとして働く。等電位面をラジアルエレメントだけで構成しなければいけないので、しっかり放射状に配置する必要がある。

大地から離れるほど、例えばマンションの高層などではカウンターポイズは十分に動作させるのが難しいと思われる。等電位面が離れすぎてエレメントの代わりにならなくなるのと、容量を確保するのが難しい。

先々週ぐらいに子どもがB型インフルエンザになっていた。

金曜ぐらいから微妙な感じだったけど、土曜日に高熱になり嘔吐したりしていた。普通の風邪かと思って出掛けて帰ってきたらゲロ臭がしててヤバい状態だった。感染拡大しないようにと消臭のために一通り次亜塩素酸ナトリウム溶液で処理したりもろもろ。

日曜日も高熱だったので、もしインフルなら早めに薬を処方してもらうべきということで夜間休日外来を受診。このときB型と診断されタミフルを処方される (夜間休日なので1日分のみ)。待ち合い室もA型・B型インフルエンザだらけだった。

月曜日かかりつけの小児科に。8:50ぐらいに電話したけど予約が11時だった。小児科の隔離室も溢れていて外で待ったりしている状態。ここもA型とB型の人がいた。熱が下がれば最短で金曜日からは登園許可といわれた。

薬局の隔離室も溢れていてしばらく寒い駐車場で待つことに。

その後1週間はいまいち熱も下がらず咳が多かった。最短の登園許可には間に合わなかったので1週間休み。もともと喘息気味なのがぶりかえして咳がなかなか収まらなかった。

保育園がB型インフルエンザでパンデミック気味になっているようだった。