今まで SketchUp + SketchUCam で雑にポケット加工の機能を使って生成してたりしていたのですが、思うような G-Code になかなかならなくてダルくなったので自力で書くことにしました。

加工範囲ぎりぎりを動かすので、いつもリミットにあたらないか心配しながらやってたのですが、そのへんを考慮するようにしました。

今いちスタンダードなやりかたがわからないのですが、中央から削っていくようにしてみました。このほうがゴミが残りにくいかな?

あと面出しって英語でなんて言うかわからなくて検索できませんでした。

#!/usr/bin/env ruby -v

io = $stdout

=begin
使いかた:
 1. 面出し範囲のX軸幅・Y軸幅を測ってパラメータを変えておく
 2. 面出し範囲の左下に原点をあわせる
 3. G-code を生成して流す
=end

# 加工面のX軸の幅
x_size = 201
# 加工面のY軸の幅
y_size = 151
# 削る厚さ
cut_thikness = 0.1
# 使用するエンドミルの直径
tool_diameter = 6.0
# 削りのオーバーラップ割合
overwrap = 0.9
# 加工フィードレート
feed   = 500
# セーフレベル
z_safe = 1

###
io.puts "G94 ( mm/min feed rate. )"
io.puts "G21 ( Use mm )"
io.puts "G90 ( Absolute distance )"
io.puts "M3 S10000 (Start Spindle)"
io.puts

# 原点が面出し表面の左下にあること前提
# 念のためリミットにひっかからないことを確認するため、最初に外形を一周する
# この生成プログラムでは座標が加工原点からマイナスにいったり、
# 設定した x_size/y_size を超えることはない
# 一方で、加工原点や x_size/y_size からはエンドミル半径分はみだして切削する
# これは削り残しがないようにするためで意図的

io.puts "G0 Z%.5f" % [z_safe]
io.puts "G0 X%.5f Y%.5f" % [0, 0]
io.puts "F%.5f" % [feed]
io.puts "G1 Z%.5f" % [-cut_thikness]
io.puts "G1 X%.5f Y%.5f" % [0, y_size]
io.puts "G1 X%.5f Y%.5f" % [x_size, y_size]
io.puts "G1 X%.5f Y%.5f" % [x_size, 0]
io.puts "G1 X%.5f Y%.5f" % [0, 0]
io.puts "G0 Z%.5f" % [z_safe]

# 中央から外形に向かって削る
begin
	x_steps = ( x_size / (tool_diameter * overwrap) ).ceil
	y_steps = ( y_size / (tool_diameter * overwrap) ).ceil
	steps = [x_steps, y_steps].min

	# 切削中のrectサイズ
	x_center = x_size / 2.0
	y_center = y_size / 2.0
	x_current_size = 0
	y_current_size = 0

	# 削りはじめ、XとYで差があるので、まずはそれを埋める
	case
	when x_size > y_size
		x_current_size = (x_steps - y_steps - 1) * (tool_diameter * overwrap)
		x_offset = (x_size - x_current_size) / 2.0

		io.puts ""
		io.puts "(initial line)"
		io.puts "G0 Z%.5f" % [z_safe]
		io.puts "G0 X%.5f Y%.5f" % [x_offset, y_center]
		io.puts "G1 Z%.5f" % [-cut_thikness]
		io.puts "G1 X%.5f Y%.5f" % [x_offset + x_current_size, y_center]
	when y_size > x_size
		y_current_size = (y_steps - x_steps - 1) * (tool_diameter * overwrap)
		y_offset = (y_size - y_current_size) / 2.0

		io.puts ""
		io.puts "(initial line)"
		io.puts "G0 Z%.5f" % [z_safe]
		io.puts "G0 X%.5f Y%.5f" % [x_center, y_offset]
		io.puts "G1 Z%.5f" % [-cut_thikness]
		io.puts "G1 X%.5f Y%.5f" % [x_center, y_offset + y_current_size]
	else
		# nothing to do
	end

	# 少しずつ広げながら削る
	# 外形は最初に一度削っているが、内側の削りカスを除去するためにも再度削る
	steps.times do |step|
		x_current_size += (tool_diameter * overwrap)
		y_current_size += (tool_diameter * overwrap)
		xx = x_current_size / 2.0
		yy = y_current_size / 2.0
		io.puts ""
		io.puts "(step %d)" % step
		io.puts "G1 X%.5f Y%.5f" % [x_center + xx, y_center + yy]
		io.puts "G1 X%.5f Y%.5f" % [x_center - xx, y_center + yy]
		io.puts "G1 X%.5f Y%.5f" % [x_center - xx, y_center - yy]
		io.puts "G1 X%.5f Y%.5f" % [x_center + xx, y_center - yy]
		io.puts "G1 X%.5f Y%.5f" % [x_center + xx, y_center + yy]
	end
end

io.puts "G0 Z%.5f" % [z_safe]
io.puts "M5 (Stop Spindle)"
io.puts "M2 (Program End)"

% 演算子とか、もろもろが便利なので Ruby で書きましたが、再代入禁止の変数が欲しいですね。定数だとおおげさなので……

  1. トップ
  2. tech
  3. CNC フライスで加工台の面出しをするG-Codeを生成するRubyスクリプト

Universal Foldable Keyboard のレビュー

結局 amazon.com (US) から買ってみました。ちゃんと技適マーク付きのが来ました。日本で買うより安いです。

説明書が入ってなくて、なんかよくわからんイラストだけです。IKEA の説明書みたいなやつと言えばわかる人にはわかるでしょう。

配列自体は変態配列で、たとえ US 配列でも普通のキーボードと比べて打ちやすくはないです。右側はキー数が多いせいでピッチが狭かったり、中央付近で折れる構造のために中央付近のキーがなんか変な感じがします。

よくできてるなと思ったところは

  • 同時に2つペアリングができて、ボタン1発で切替えができる
  • OS切替ボタンがあって、Windows/Android/iOS でキーマップが変わるようになっている

あたりです。

同時に2つペアリングするやつは、それぞれに別々の BLE アドレスがふられているので、近くにある別々の機器に同時にペアリングして、ボタンで切替えながら入力先を変更できるので、Windows/Mac で開発しながら、Android もデスクに置いて切替えて入力するみたいなことができて便利そうです。

Universal Foldable Keyboard の仕様

まぁレビューするために買ったんじゃないんですよ。BLE キーボードの参考実装として買ったのです。ということで以下技術的な仕様をレビューします。

GATT 構造のダンプ

以下のようになっています。Device Information の Firmware Revision に CSR uEnergy SDK 2.4.2 と入っていたので、CSR のチップなのでしょう。Unknown Service が2つありますが、CSR の DFU まわりのためのサービスのようです。

HID Information の Base USB Specification Version に 0x213 が入っていて異常です。HID のスペックは 1.11 が最新のはずなので、(BCD で) 0x111 とかが正しいと思うのですが、2.13 という謎のバージョンになっています。

Device Information Service の PnP ID は source=USB VID=0x45e PID=2060 version=272 でした。

Connection Parameter は

  • 15ms - 15ms
  • Slave Latency 33
  • Supervision Timeout: 600

でした。

他には特に気になるところはありません。

Generic Attribute (0x1801)
- Service Changed [I] (0x2A05)
   Client Characteristic Configuration (0x2902)
Generic Access (0x1800)
- Device Name [R W] (0x2A00)
- Appearance [R] (0x2A01)
- Peripheral Preferred Connection Parameters [R] (0x2A04)
Human Interface Device (0x1812)
- Report Map [R] (0x2A4B)
   External Report Reference (0x2907)
- Report [N R] (0x2A4D)
   Client Characteristic Configuration (0x2902)
   Report Reference (0x2908)
- Report [N R] (0x2A4D)
   Client Characteristic Configuration (0x2902)
   Report Reference (0x2908)
- Report [R W WNR] (0x2A4D)
   Report Reference (0x2908)
- Report [R W] (0x2A4D)
   Report Reference (0x2908)
- HID Information [R] (0x2A4A)
- Boot Keyboard Input Report [N R] (0x2A22)
   Client Characteristic Configuration (0x2902)
- Boot Keyboard Output Report [R W WNR] (0x2A32)
- HID Control Point [WNR] (0x2A4C)
- Protocol Mode [R WNR] (0x2A4E)
Battery Service (0x180F)
- Battery Level [N R] (0x2A19)
   Client Characteristic Configuration (0x2902)
   Report Reference (0x2908)
Unknown Service (a74df799-13fd-4f82-a45a-0340180eac97)
- Unknown Characteristic [R W] (343f8f87-ec68-41a7-a97f-3141b2424e1d)
Unknown Service (00001016-d102-11e1-9b23-00025b00a5a5)
- Unknown Characteristic [R W] (00001013-d102-11e1-9b23-00025b00a5a5)
- Unknown Characteristic [W] (00001018-d102-11e1-9b23-00025b00a5a5)
- Unknown Characteristic [N R] (00001014-d102-11e1-9b23-00025b00a5a5)
   Client Characteristic Configuration (0x2902)
Device Information (0x180A)
- Firmware Revision String [R] (0x2A26)
- PnP ID [R] (0x2A50)
Scan Parameters (0x1813)
- Scan Interval Window [WNR] (0x2A4F)
- Scan Refresh [N] (0x2A31)
   Client Characteristic Configuration (0x2902)   

HID ReportMap

Report Map を読み出して、人力でパースしました。自動的にパースしてダンプしてくれるツールってないんですかね? おかげで人力で結構 HID Report Map が読めるようになってしまいました。

概要としては

  • report id = 1 が通常のキーボードのレポート
    • ただし feature report の定義もある (詳細は不明)
  • report id = 3 がメディアキーとかのレポート
    • 音量とか再生ボタンとか

report id = 2 が抜けてますが、Report Map Characteristics に External Report Reference Descriptor がついており、Battery Level Characteristics の UUID が入っていました。そして Battery Level Characteristics に Report Reference Descriptor がついており、report id = 2 / input report として定義されていました。これって HID の Report Map の中には定義しなくていいんでしょうか? HOGP とかの仕様書を読んでもどうしてもよくわからないのですが…… HID にもバッテリー情報を送る決まりはありますが、それは関係ないんでしょうか。(というか BLE のバッテリー情報に主要OSが対応してないのでどうしよもないです)

feature report の仕様はよくわかりません。MS 提供のツールで、言語設定を上書きできるので、これの操作に使われているような気がしています。

05-01 Usage Page (Generic Desktop)
09-06 Usage (Keyboard)
A1-01 Collection (Application)
	85-01 Report ID (0x01)
	05-07 Usage Page (Key Codes)
	19-E0 Usage Minimum (224)
	29-E7 Usage Maximum (231)
	15-00 Logical Minimum (0)
	25-01 Logical Maximum (1)
	75-01 Report Size (1)
	95-08 Report Count (8)
	81-02 Input (Data, Variable, Absolute); Modifier data

	95-05 Report Count (5)
	75-01 Report Size (1)
	05-08 Usage Page (Page# for LEDs)
	19-01 Usage Minimum (1)
	29-05 Usage Maximum (5)
	91-02 Output (Data, Variable, Absolute) ; LED report

	95-01 Report Count (1)
	75-03 Report Size (3)
	91-03 Output (Constant, Variable, Relative) ; padding

	95-07 Report Count (7)
	75-08 Report Size (8)
	16-00-00 Logical Minimum (0)
	26-FF-00 Logical Maximum (255)
	05-07 Usage Page (Key Codes)
	19-00 Usage Minimum (0)
	2A-91-00 Usage Maximum (145)
	81-00 Input (Data, Array, Absolute); Key arrays

	05-0C Usage Page (Consumer)
	0A-C0-02 Usage (0x02c0/704 ?)
	A1-02 Collection (Logical)
		85-01 Report ID (0x01)
		1A-C1-02 Usage Minimum (705)
		2A-C6-02 Usage Maximum (710)
		75-08 Report Size (8)
		95-06 Report Count (6)
		B1-03 Feature (Content, Variable, Relative)
	C0 End Collection
C0 End Collection

05-0C Usage Page (Consumer)
09-01 Usage (Consumer Control)
A1-01 Collection (Application)
	85-03 Report ID (0x03)
	75-10 Report Size (16)
	95-01 Report Count (1)
	15-01 Logical Minimum (1)
	26-FF-03 Logical Maximum (1023)
	19-01 Usage Minimum (1)
	2A-FF-03 Usage Maximum (1023)
	81-60 Input (No Preferred, Null state)
C0 End Collection

ドライバ

Windows に接続すると、専用のドライバが読みこまれることを確認しました。なんのためにそうなっているのかよくわかりませんが、明確に Windows の汎用 kbdhid ドライバとは違いがあります。

自作キーボードを Universal Foldable Keyboard に成り済まして検証する

だいぶあれ気ですが、PnP ID を偽称して Universal Foldable Keyboard に成り済まして消費電流を検証しました。Universal Foldable Keyboard を買ってやりたかった一番のことがこれです……

結果的に、汎用 kbdhid ドライバだと 800μA ぐらいの消費が、Universal Foldable Keyboard のドライバを読みこませることで 200μA まで減るということがわかりました。ドライバ内でなにかしら調節しているようです。チートかよ。

備考:Windows のドライバロード手順

デバイスが接続されると以下のディレクトリの .inf からデバイスドライバが検索される。BLE 接続後、しばらくプログレスバーが表示されるが、これを検索している時間分待たされるっぽい。

C:\Windows\System32\DriverStore\FileRepository

.inf の中に以下のような行がある。

%UniversalFoldableKeyboard.DeviceDesc% = UniversalFoldableKeyboard, HID\{00001812-0000-1000-8000-00805f9b34fb}_Dev_VID&02045e_PID&080c&Col01


ここのやつがデバイスとマッチするとドライバがロードされる。VID/PID は PnP ID、Col01 は HID の Report Map の中のコレクションの Report ID ?っぽいがよくわからない。Collection もうまく一致していないとロードされない。

一致するドライバがみつかると C:\Windows\INF 以下に oemNN.inf oemNN.PNF としてコピーされる。次回からはここの .inf が読まれるのですぐ接続される。

  1. トップ
  2. tech
  3. Microsoft Universal Foldable Keyboard (US版) を買ってみたら辛い事実がわかった

背景

現在は CNC のコントローラとして Grbl + 自作のインターフェイスを使っています。Grbl の G-code インタプリタは必要な機能はほとんどどありますが、凝ったことをしようと思うと少し困ることがでてきます。

ということで、Beagle Bone Black と Machinekit (Linux CNC) での環境構築をぼちぼちはじめています (まだある程度設定しただけで動かせてませんが)。その過程で結局コードを読むハメになってるので覚書を残しておきます。

Machinekit とは何か

Machinekit は Linux CNC (EMC2) からの fork プロジェクトです。Linux CNC は x86 しかサポートしていませんが、Machinekit は ARM もサポートしています。細かい違いがいろいろあるみたいですが、実のところよくわかってません。

OSSのG-code実行機だと Linux CNC が最も高機能なようなので、これが動く環境がうまく作れれば、機能で困ることはなくなるはずです。

Machinekit / Linux CNC はどのようにして GPIO を操作するか

名前の通りなのですが、これらは Linux 上で動きます。Linux CNC の典型的な実行環境は、普通の x86 コンピュータにパラレルポートを付けたマシンです。パラレルポートをGPIOとして使用し、ステッピングモータドライバなどに送る信号を出力します。

全てソフトウェアで制御信号を生成するため、Xenomai という Linux カーネルにリアルタイム拡張を行うものを組込み、複数のリアルタイムスレッドを協調して動かすことでスムーズに実行できるようにしてあります。

具体的には

  • base-thread
    • 25μs ごとに起動
    • 最小の実行単位を扱うスレッドで、GPIO の実際の操作を行う
  • servo-thread
    • 1ms ごとに起動
    • 入力などを処理して base-thread で行う操作を決めるスレッド

という2つのスレッドがあります。実行間隔を書きましたが、実際には Xenomai を組み込んだ状態でも各スレッドが起動される間隔にはばらつきがあり、正確に起動されるわけではありません。「リアルタイム」はあくまで最悪の応答時間を保証しているだけです。十分余裕を持って行動するために処理の重さに応じて実行スレッドが分けられているわけです。

Machinekit と Beagle Bone Black

Beagle Bone Black は TI の ARM SoC である AM3359AZCZ100 がメインCPUの、Raspberry Pi に似たカードサイズ Linux コンピュータです。GPIO が豊富にあり、これに Machinekit をインストールすることで、単体でCNCコントローラにできます。

パラレルポートに依存した環境構築というのは今時ちょっとやる気が起きませんし、パラレルポートが増設可能なフル装備の Linux コンピュータを組み立てようと思うと結構コストがかかります。Beagle Bone だと単体で役目を果たすことができます。

Raspberry Pi ではダメなのか? という疑問があるかと思いますが、実は BBB にあって Raspberry Pi にはない重要な機能があります。それが PRU (Programmable Real-time Unit) です。

PRU は要するにメインのCPUと独立して動作できるマイコンです。メインCPUは1GHzですが、PRU は 200MHz の独自クロックで、メインCPUとは独立して動作します。

PRU の実装自体は独自の命令セットのアセンブリを書いて、TI 提供のツールでコンパイルして実行バイナリを得ます。なので、あまり高度なことをやるのは難しいですが、CNC の制御信号を出すというような用途にはまさにうってつけです。

フツーのPCと何が変わるか

Machinekit のリアルタイム処理は base-thread と servo-thread に分かれていると書きましたが、BBB の場合は base-thread は存在せず、servo-thread だけがあります。base-thread 相当の処理が PRU で独立して行われます。

Machinekit の hal_pru_generic ドライバにおいて PRU が GPIO を操作する間隔はデフォルトでは10μsとなっています。これは普通のPCのデフォルトの2倍以上の速度ですが、内部的には PRU 内でビジーループでえ 10μs を待つように実装されています。GPIO まわりのタスク処理が 10μs すなわち 2000 CPUサイクル以内ならば遅延することがありません。

現状では

  • stepgen
    • ステッピングモータの駆動パルス生成
  • pwmgen
    • ソフトウェア PWM
  • encoder
    • エンコーダーパルスをなんかするやつ(使ったことないです)

を PRU 内で実行できます。

PRU と PRU Low Latency I/O

AM3359AZCZ100 にはPRU Low Latency I/O というのがあり、複数の特定ピンの GPIO を PRU 内から r30/r31 レジスタへの読みかきによって1サイクルで行えるというものすごいものがあります。

一方で、PRU 内からだからといって PRU Low Latency I/O しか使えないというわけでもなく。普通の GPIO もそれよりはレイテンシがありますが読み書き可能です。

Machinekit の実装でもいずれのピン設定も利用可能です。前述の通り 10μs ごとの操作になるので、特に Low Latency ピンにこだわる必要はなく、この場合特にメリットもありません。

このへんは HAL の設定まわりになるので、ちゃんと動かせてからそのうち書きたいと思います。

  1. トップ
  2. tech
  3. Machinekit (Linux CNC) のアーキテクチャと、BeagleBone Black での動作