CNCフライスでMDF板を面だしする

はじめに

NCフライスに用いる被切削材の土台として、MDF板を使用したい。土台として使用する前に、面を削って平坦化する(面だし)。
今回の場合、四隅をクランプで固定+両面テープで固定し、S12000、F800にて切削を行うと上手く面出しを行える。
f:id:sosoru_m:20170106201133j:plain:w300

条件

  • スピンドル回転数S 12000rpm
  • 送り速度F 800mm/min
  • 被切削材 厚さ9mmのMDF板
  • 直径2mmの超硬エンドミルを使用.

板の表面全面を深さ2mm削り取る(本当は0.5mmぐらいで良いはず.切削材が反って失敗したため大きめに削り取っている.)

流れ

今回は,実際に主軸スピンドルを回転させて,実際に切削する様子を見るのが一つの目的である.ある種,定番となっている土台の面出しをさせてみる.
また,前回,NCフライスの動作確認としてボールペンの替え芯を主軸に持たせてプロットを行ったが,土台の歪みにより,プロット時に位置に依存した歪みが生じていた.今回は,厚さ9mmのMDF板を面出しし,これをベースにプロットすることで,低歪みの地図を見たい.

しかし,面出し一つでも上手く切削することができなかった,どうやら,MDF板の固定が甘く,板の中心から離れた場所を削ろうとすると,板が土台から剥がれ凹型に反り,結果として面を削りすぎてしまう.

f:id:sosoru_m:20170106201133j:plain:w300
▲両面テープを5本にしたまま,クランプで4隅を固定した.この方法であれば上手く面出しできる.
ただし,クランプの影響で,板全面は削れない,余白ができる.

試行錯誤する中で送り速度を下げてみたが,あまり影響はなかった.板を固定する方が重要である.
面出しできたので,前回の地図をプロットした.

f:id:sosoru_m:20170106201409j:plain:w300
▲面だし時の木くずの影響か,インクの出ない部分があるが,上手く角が出ている.ペンの軌跡を見る限り,歪みはなさそうだった.

f:id:sosoru_m:20170106201454j:plain:w300
▲確認できる限り,最小のピッチは0.25mm程度だった.寸法通りの間隔で線が引けている.

反省点

木を切削クズが予想よりも厄介で,ちり取りだけでは処理できない.掃除機が必要.アルミ等の金属であれば切削液が使えるので,今回よりも影響が少なそうであるが,木材に水は使えない,結局のところ,集塵機の装備も必要.
面だししても,MDF材は水に弱い.切削液を使用する切削には使えない.値は張るが,厚めのアクリル板等が必要.ケチるならスチレンボード等も使えるかもしれない(耐水性があれば).

CNCフライスで地図を書かせる

はじめに

NCフライスの動作を確認するため、主軸にボールペンの替え芯を持たしてプロットさせた。
紙の土台の反り、ボールペン芯の歪みの問題が発生したものの、
結果として、0.25mmピッチ(目視)でのプロットが可能であることを確認した。

f:id:sosoru_m:20170106201409j:plain:w300

条件

  • 送り速度 F 200 mm/min
  • Z軸送り速度 Fz 200 mm/min

(Z軸送り速度も同じ速度にする。終点まで書き終わらずに引き上がる可能性あり)

  • ボールペンの替え芯を主軸に取り付けるため、3.125mmのコレットを使用して装着。

使用した替え芯はuni JetStream 0.5mm SXR-80-38である.(売り場で定規を当てて測って決めた)そのままでは長すぎたため,半分程度に切断した.

プロットしたファイル

川崎市の地図。下のサイトから拝借
http://www.dtpmap.com/free_9_11.html

dxfファイルをGコードに変換するにはdxf2gcodeを用いた.(途中からVCarveに切り替えたが,大きな変更はない)

流れ

f:id:sosoru_m:20170106223753j:plain:w300
▲最初、百均で買ってきた下敷きをマスキングテープで貼り付け、その上に紙を乗せた状態でプロットしていた。LinuxCNCのサンプルデータは割ときれいに出力できるが、角の多い図形、たとえば地図をプロットさせると、軸の移動時に替え芯が歪み、角が丸まったままプロットされる。このままでは精度のよいプロットはできない。

f:id:sosoru_m:20170106223115p:plain:w300
▲動作確認用として、15cm四方に3mm角を敷き詰めたdxfファイルをプロットさせたところ、プロットする位置によっても図形の歪みが見られる。これは土台として使用している下敷きの反りによるものであった。

下敷きのエッジをテープで固定しただけでは、反りを押さえ込むのは不十分なようなので、両面テープ(再生紙のポピュラーなやつ)で底面とフライス盤とを固定した。また、替え芯を切り詰めて軸の歪みを抑えた。この方法は割とうまくいくようで、図形の歪みが改善されているように見える。

f:id:sosoru_m:20170106202501j:plain:w300
▲地図をプロットしてみても、先ほどよりはだいぶ改善されたが、未だ位置に依存した図形の歪みが見られ、完全ではない。

f:id:sosoru_m:20170106201409j:plain:w300
▲その後、面出しを行ったMDF板を用い、これを土台にしてプロットを行った。(面出しの記事は別記事参照)紙はエッジをテープにて固定する。
一部インクが出ていないが、この方法であれば、きちんと歪みなくプロットされていることがわかる。

結論

土台に関しては、エッジをテープで固定するだけでは不十分で、両面テープを用いても、とても精度のよい結果が得られるとは言えない。これは薄いアクリル板(1mm)の使用が原因だろう。
面出しを行った9mmのMDF板を両面テープ+4隅をクランプで固定した場合に、最も精度よくプロット可能である。

LinuxCNCでCNCフライスを制御する

はじめに

一般的にNCフライスの制御にはMach3が使われることが多い.しかし,複雑な切削を行う場合(=長いGコードを読み込む場合)には,有料のライセンスを購入する必要があり,ソフトの機能に制約がある.
一方で,LinuxCNCと呼ばれるオープンソースなNCフライス制御ソフトも開発されており,こちらには機能制約はない.(機能的にはこちらの方が汎用的らしい?)
購入したNCフライスはMach3を想定して設定されているが,ライセンス代をケチるためにLinuxCNCで頑張ってみることにした.

なお,使用したNCフライスは以下の「黒い奴」である.

item.rakuten.co.jp

黒い奴にはMach3用の設定が提供されているので、これを元にLinuxCNCを設定する

コマンド

↓LinuxCNCの起動(レイテンシ調査もこっち.Sample Configurations->apps->latency.ツリービューを色々弄ると見つけられる.生成した設定ファイルはMy Configurationsに登録されるはず.)

./linuxcnc

↓LinuxCNC設定ファイル生成画面の起動

./stepconf

LinuxCNCのインストール

以下のサイトから落としてくれば良い.リアルタイム性能を引き出すために,Linuxカーネルに細工をする必要があり,細工済みの起動イメージが配布されている.これを用いるのが一番楽だ.
これを書いている人は,ソースからビルドを試みたが,あまりに躓く点が多いので,一度諦めている.

linuxcnc.org

配布されている起動イメージではvncserverが入っていない.また,sshd_configではパスワード認証を許可していないので,ログイン周りで弄らないといけないかもしれない.

起動出来た場合,最初にlinuxcncのレイテンシを調べておく(latency-histogram).レイテンシが数usec以内に収まっていない場合には,対策が必要かもしれない.
レイテンシ軽減の一つの策として,カーネルの起動コマンドにisolcpusを追加し,割り込み用のcpuを定義する方法がある.レイテンシは減るが,実質的にcpu数が一つ減るので処理速度とのトレードオフである.

f:id:sosoru_m:20170106180857j:plain
▲レイテンシの少ない例。isocpusを有効にした場合さらに改善する.

f:id:sosoru_m:20161024215649p:plain
▲レイテンシが多い例.カーネルレベルでのリアルタイム処理の対策を行っていない.

レイテンシはすなわち出力波形のジッターとして出てくるはず.オシロスコープで実際の波形を見たわけではないが,ジッターはレイテンシ以下の数値にはならないと思う.

stepconf

レイテンシが満足できたら,次はNCフライス制御の設定を行う.
パラレルポートのピン位置や,軸の回転ステップ数や範囲についてを設定する.
LinuxCNC 2.7.8ではMach3の設定を読み込めるが,読み込んだ全ての設定が反映されているかはとても怪しい.(ピン位置の設定は反映されていなかった)

Mach3の設定画面を見つつ,stepconfを進めていけば良い.


f:id:sosoru_m:20170106182524p:plain
▲この画面はあまり設定を変更する必要がなかった.Mach3のKernelSpeedとだいたい一致するように Max step rate を設定してやればよい?

f:id:sosoru_m:20170106183023p:plain
▲ピンアサイン設定.ここは悩まされた。
Mach3と対応していない一部の設定項目について説明する.StepやDirection,Home,EStopは省略する.

項目 Mach3での呼称 説明
Spindle PWM Spindle 主軸スピンドルに与えるPWM信号.
Amplifier Enable Enable1 ステッピングモータの通電切り替え?
Spindle On Output #1 スピンドル回転のオン/オフ
Coolant Flood Output #2 クーラントのオン/オフ(任意負荷)

Mach3のLowActiveはstepconfのinvertedと対応する.
また,EmergencySwitchの入力信号が,Mach3と論理が一致しないようなので,注意する.
Mach3を使いこなしていないから,OutputやEnableに関する設定がどのようにソフトの挙動と対応するかは知らない.これは試行錯誤で設定を行った.
ちなみに,二つ先の画面で軸の移動量をテストする機能があるので,ピンアサインに関して一部動作確認できる.

f:id:sosoru_m:20170106184455p:plain
▲これは重要ではない.オプションでおまけのGUIを表示できるよってやつ.スピンドル回転数などを表示するときに用いるらしい.

f:id:sosoru_m:20170106184837p:plain
▲これは重要.軸の移動量を決定する.灰色背景のテキストボックスは「編集可能」,もっとユーザビリティを考えてほしい.Mach3ではmmあたりのステップ数(画像一番下の数値)のみ決定するが,LinuxCNCでは一回転あたりのステップ数を指定する.軸のリードピッチが分からないと変換できないので,調べる必要がある.
右上のボタンで移動量をテストできる.(もちろん,ピンアサインが間違っていたら動かない.)

f:id:sosoru_m:20170106185605p:plain
▲重要だが躓く要素は少ない.PWM RateはMach3でも同様の設定項目はある.画像では最高回転数の時をPWM(=Duty比)1.0と設定している.

これで設定は終了で,あとは設定ファイルを保存するか聞かれるはず.設定をLinuxCNCに読ませて上手く動けば成功.

おわり

以上,是非とも設定で躓き,泥水を啜り,地べたから這い上がりつつも,なんとか動作確認をすませ,時間を浪費してほしい.なお,Mach3を2万円で購入するのであれば,以上の設定はすべて無駄である.

LinuxCNC(uspace ver) On CentOS6.8 の導入

(2016/10/25追記,CentOS 6.5として扱ってたけど,6.8でした.ごめんなさい.タイトル修正済み)

目的

古いマシン(Intel Atom 330 @ 1.60GHz, 2048GB RAM)を使って,NCフライスを制御したい.CentOS 6.56.8のほぼminimalな環境は整備されていたので,まずは,非リアルタイムなLinuxCNCが走るか確認した.

とりあえず,起動出来た感じ.

準備

  • LinuxCNCはウィンドウを出してくるので,とりあえずデスクトップ環境を入れておく.

vncserverも設定しておいた.

http://linuxcnc.org/docs/devel/html/code/building-linuxcnc.html
を見ながら作業を進めた.

configure編

最終的には下記コマンドでconfigure可能:
./configure --with-realtime=uspace --without-libusb-1.0 -enable-non-distributable=yes

ハマったこと:

  • usblib-1.0が入らない?usblibがあるけど認識していない.今回は必要ないので,--without-libusb-1.0を付加
  • libmodbusの導入.configureはパッケージの有無を確認しているので,rpm化する必要あり
  • tkimgの導入.Debian系ではパッケージあり,CentOSではナシ.Img-Sourceを落としてきてmakeする.--prefixが指定されていないので注意
  • -enable-non-ditributable=yesが必要?
  • Python2.7の導入.CentOS6.5の公式リポジトリだと2.6なので,2.7を導入する必要あり.(configure時に--enable-sharedを付けないと,make時のリンクで引っかかる.)(2.6と切り替えられるようにしないとyum等が動作しない.update-alternativesで対応可)
  • 他にも足りないの聞かれるが,公式リポジトリからyum installで対応可

make編

configureしたらmakeすればおk.

ハマったこと:

  • gcc(stdlibc++)4.9.4の導入.公式リポジトリは4.4.7なので,C++11系コードをコンパイルできない.(atomicを使ってる)(gccのmakeでmakeinfoないって言われる.texinfoパッケージ入れる→config.status内のmissing makeinfo→makeinfoに変更で解決?)
  • boostのバージョンが古い?BOOST_FORCEINLINEに対応してないので,1.6.2をいれる

実行編

linuxcnc-dev/script/linuxcncを実行.メガネペンギンが出てくれば成功

f:id:sosoru_m:20161024215649p:plain
↑latency-histogramの実行結果.右が1000usec周期でパルスを出す場合.真ん中がとんがってる(=遅延なしの場合が多い)ので頑張ってるけど,プロットの枠外に広がるほどバラツキがあるようだ.左は25usec.遅延バラバラで全く駄目.

補足(TCLLIBPATHの結合がおかしい?)

latency-histogramを実行すると,tclshがpackage require Img出来ないとぬかしてくる.
linuxcncは,tclshに渡す前にrip-environmentを実行して環境を整えてくれているのだけど,環境変数$TCLLIBPATHを再定義する際に,パス文字列の結合にスペースでは無くコロンを使っているので,tclshは正しくパッケージを読み込めなくなるみたい.

scripts/rip-environment:

if [ -z "$TCLLIBPATH" ]; then
    TCLLIBPATH=$EMC2_HOME/tcl
else
    TCLLIBPATH=$EMC2_HOME/tcl:$TCLLIBPATH

↓

if [ -z "$TCLLIBPATH" ]; then
    TCLLIBPATH=$EMC2_HOME/tcl
else
    TCLLIBPATH="$EMC2_HOME/tcl $TCLLIBPATH"

にすれば動いてくれる.

つづく

予想通り普通のカーネルでは役に立たないので,リアルタイムカーネルを入れましょう.つづく

Accessが複数開かれている時にGetObject()する方法

はじめに

  • ExcelAccessをはじめとしたOffice系のソフトは,オートメーションなる機構が準備されているので,各々連携することが可能である.
  • 具体的には,CreateObject()やGetObject()といった関数で,各ソフトのApplicationオブジェクトに相当する参照を取得でき,例えば,ExcelからAccessにクエリを投げて,得られた結果をワークシートに転送する,といったマクロを作成可能である.
  • いや,MSDN*2は最初の引数に使いたいファイルパスを指定すれば選択できそうな雰囲気ではあるが,試してみたところ,新しいインスタンスが開かれ,取得された参照が破棄されると同時にインスタンスも閉じられた.うまくやる方法があるのかもしれないが,ここでは操作するインスタンスを指定できないとしよう.
  • そこで,ウィンドウタイトルから開いているファイルを推測し,そのウィンドウのハンドルを基にしてAccessibleObjectFromWindow()関数を呼び出し,目的のインスタンスの参照をつかむ方法が,首尾良く行きそうなので報告する.

AccessibleObjectFromWindow()関数

MSDN*3を参照.

こいつでウィンドウハンドルから,インスタンスの参照を得られる.VBAでは引数の扱いに注意が必要な点がある.

  • 第2引数は取得したいオブジェクトのIDを渡す.Office系の参照が欲しい場合はOBJID_NATIVEOMを指定する.
  • 第3引数の REFIID riid はGUID渡すべきであるが,VBAのGUIDを指定すると怒られる.Declareな関数では使えないらしい?ので,同等の構造体をユーザー定義する.Office系の参照を得るためには,IDispatchに相当するGUIDを渡す.

EnumWindows()関数

MSDN*4を参照.

ウィンドウを列挙してくれる.第1引数にコールバック関数を渡す必要があるが,なんと,VBAはAddressOf演算子を使えば渡せるらしい.

GetWindowText()関数

MSDN*5を参照.

ウィンドウタイトルを得る.VBAでは固定長文字列を宣言する方法があって,宣言の後に「* 256」を付け足せば良いらしい.

Access複数開かれている場合に参照を取得する例

  • Access複数開かれている場合を例にした実装を張る.
  • Main()からEnumWindows()を呼び出し,目的のインスタンス("tesuto.accdb"なるファイルを開いたAccessインスタンス)が存在したら,フォーム"hoge"を開く.
  • EnumWindows()のコールバック関数では,ウィンドウタイトルを調べ,目的のファイルを開いていそうだったら,Accessインスタンスの参照の取得を試みる.
Option Explicit

Public AccessWndHandle As Long
Public AccessApplication As Object

Private Const OBJID_NATIVEOM = &HFFFFFFF0

Private Declare Function GetWindowText Lib "User32" _
Alias "GetWindowTextA" _
(ByVal Hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long

Private Declare Function AccessibleObjectFromWindow Lib "oleacc" _
(ByVal Hwnd As Long, ByVal dwId As Long, _
ByRef riid As UUID, ByRef ppvObject As Object) As Long
     
Private Declare Function EnumWindows Lib "User32" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long

Private Type UUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type

Public Function EnumWindowsCallBackFunc(ByVal Hwnd As Long, ByVal lParam As Long) As Boolean
    Dim title As String * 256

    If False = GetWindowText(Hwnd, title, Len(title)) Then
        GoTo Continue
    End If
    
    If InStr(title, "tesuto") > 0 And InStr(title, "Access") > 0 Then
        Dim IID_IDispatch As UUID
        With IID_IDispatch
            .Data1 = &H20400
            .Data4(0) = &HC0
            .Data4(7) = &H46
        End With
        
        If 0 <> AccessibleObjectFromWindow(Hwnd, OBJID_NATIVEOM, IID_IDispatch, AccessApplication) Then
            GoTo Continue
        End If
        
        AccessWndHandle = Hwnd
        EnumWindowsCallBackFunc = False
        Exit Function
    End If
    
Continue:
    EnumWindowsCallBackFunc = True
    Exit Function
End Function

Public Sub Main()
    AccessWndHandle = 0
    Call EnumWindows(AddressOf EnumWindowsCallBackFunc, 0)
    
    If AccessWndHandle = 0 Then
        Exit Sub
    End If
    
    AccessApplication.DoCmd.OpenForm "hoge"
End Sub

*1:インスタンスとは,現在コンピュータで動いているプログラム,と取って欲しい.例えば,タスクマネージャで「EXCEL.EXE」が複数存在するとき,複数インスタンスが存在すると表現する.

*2:GetObject 関数 (VBScript)

*3:AccessibleObjectFromWindow

*4:EnumWindows 関数

*5:GetWindowText 関数

Twitterのロリコン人工無能Bot @jsfavo について

@jsfavoとは

JSこれくたー,Twitterを監視する淫らなbot君です.

・@sosoruと@jsfavoのHomeTLを見て,指定されたキーワードが入ってるとふぁぼる
・30分おき(朝7時~9時は10分おき)に何かをツイート.深夜帯はお休み
・リプライを送ると何か返してくる

ふぁぼ機能

  • @sosoruと@jsfavoのHomeタイムラインを常に監視しています.指定されたキーワード,もしくは,キーワードの類似語が含まれていたらふぁぼります.
  • UserStreamを使って監視していて,見つけたらすぐにふぁぼる仕様でしたが,何処からか圧力が掛かったので,いくらかの遅延を持たせてふぁぼらせています.

f:id:sosoru_m:20160201024001p:plain
🔺こんな感じ.(TriggeredFilterが反応した単語)

f:id:sosoru_m:20160201024442p:plain
🔺類似語の場合はこんな感じ

ツイート機能

  • 予め登録されたキーワードを元に何か文章を作成します.文章の素は@sosoruの過去のHomeTLを使っています.


  • 4語のマルコフ連鎖で幾つか文章を作成していますが,そのままだと「パクツイ」になりやすいので,名詞などを類似語にランダムで置き換えています.
  • リプライを送ると,そのツイートを基に何か文章を作成して返してきます.


🔺テンプレに反応して返してくれる

  • 深夜帯の1時30分~6時前まではツイートしません.裏でデータベースのメンテナンスをしています.リプライ機能は生きているので,何かしら返してくれるかもしれませんが,メンテナンスの都合で返せない場合があります.


🔺返してくれないときもある,「わかんないよぉ」って言うときもある

リプライについて

  • 空リプを送ると何のツイートに反応したか返してくれます.


  • 何か文章を送ると,反応する単語が含まれているか教えてくれます.同時に何か喋りかけてきます.


  • 文章に「詳細」が含まれていると,さらに詳しい情報を教えてくれます.triggered filterは内部で使っている正規表現です.


  • この他にも,リプライに対して定型文を返すような設定が幾つか入っています.探してみてください.

しくみ

  • bot自体はC#で書かれていて,辞書学習等々のファイル操作でRubyをちょっと使っています.
  • C#のCoreTweetでTwiterAPIを弄りつつ,SQLServerに得たツイートを垂れ流しています.文章の形態素解析にはMeCabを使っていて,辞書には「はてなキーワード」を主に使っています.(C#側のライブラリはLibNMeCab)
  • 類似語の列挙にはword2vecを使っています.@sosoruのHomeTLをMeCabでわかち書きしたものを学習させています.(C#側のライブラリはWord2Vec.Net)

類似語について

  • word2vecを使えば単語の意味ベクトルが得られるので,2つの単語ベクトルの内積を取れば,どれだけ類似しているかが数値化できます.(これをコサイン類似度と呼ぶ?)
  • 類似語とは文脈で使われやすい単語を指しているようで,同義語と反義語の区別はないようです.

f:id:sosoru_m:20160201032919p:plain
🔺類似度の高い単語を列挙した場合,それっぽい単語がサジェストされる

f:id:sosoru_m:20160201033003p:plain
🔺類似語に同義語と反義語の区別はない?

f:id:sosoru_m:20160201033807p:plain
🔺ペアだと高い類似度を持つ?

  • 類似語の計算はとても重いです.200次元のベクトル同士を10~20万回内積計算させる処理です.キャッシュを入れましょう.
  • 最適化込みのビルドをCore i5-4670K @ 3.40GHzのマシンで並列処理させて,約100msec~200msec程度かかるようです.
  • float精度の計算なのでGPUに肩代わりさせた方が利口かもしれません.

文章生成について

  • @sosoruのHomeTLを基に4語のマルコフ連鎖で文章を作っています.2016年1月の時点で,過去1年程度のTL,300~400万ツイートがデータベースに溜まっています.
  • リプライ等々で文章生成のリクエストを受け取ると30文程度の文章を生成します.生成された文章の中から,比較的短文で,基となった単語や文章と類似したものをランダムで選び出します.基の単語と文章の類似度を測るときにも,word2vecで出した単語の意味のベクトルを利用しています.
  • 単語の意味ベクトルはword2vecが吐きだした数値を用いれば良いですが,文章の場合には,使われている名詞・形容詞などを抜き出し,その単語の意味ベクトルを足し合わせて正規化したものを,文章の意味ベクトルとして扱っています.
  • 意味ベクトルとして考慮する単語が多くなるほど,基の単語・文章との類似度が低下するので,単語数を考慮してランダムで選び出します.
  • 生成された文章と基になった文章の類似度が高い場合,幾つかの単語を類似語で置き換えています.botにたまによくある奇想天外な文章の類似度は,感覚的にあまり高くないようなので,わざと落とすことにしています.

f:id:sosoru_m:20160201040422p:plain
🔺考慮する品詞,代名詞や接尾語は見ない

f:id:sosoru_m:20160201035316p:plain
🔺「ゆのっち」を与えたときに生成された文.表示されているのは名詞や形容詞の数が2~10程度の文章.名詞・形容詞などの単語数,基の単語との類似度,生成された文章の順に表示.

辞書の更新について

  • 深夜帯にSQLデータベースのインデックス再構成と形態素解析辞書の更新,word2vecに再学習をさせています.マルコフ連鎖で使う辞書はツイートを受信した段階で,データベースに突っ込んでいます.
  • インデックスの更新が割と重要で,定期的に再構成してやらないと,参照時にタイムアウトすることがありました.

Nゲージ鉄道模型のATS・ATC案③-LEDモジュール

この記事は過去に作ったNゲージ鉄道模型ATCシステムについて記述しています.
システム全体については下の過去記事を参照してください.

sosoru.hatenablog.jp


f:id:sosoru_m:20151004222219g:plain

LEDモジュール

LEDの制御もユーザーの端末で出来るようにしていました.ATCの動作に直接の関係はありません.当時は駅ホームの照明に使っていました.(本当は信号に使うべきかもしれない)

ハードウェアについて

f:id:sosoru_m:20151018122725p:plain

Dフリップフロップ(D-FF)とバッファを組み合わせた(恐らく)ポピュラーな回路構成です.D-FFはクロック入力CLKが立ち上がった時,入力Dの状態が出力Qに保存され,次にCLKに変化あるまで,出力Qは状態を保持し続けます.幾つかのD-FFを組み合わせ,それぞれの入力を共有しておき,各D-FFことにCLKを制御することで,マイコンが扱えるIOピンが増えます.このモジュールでは8入出力D-FFを4つ使っています.

DフリップフロップICの出力ピンに流せる電流が心許ないので,バッファを入れて大電流に備えています.

ちらつかない程度な低周波の128段階PWM波形を生成し,一つのモジュール当たり,32個のLEDを駆動することが出来ます.実際には出力を30個に切っています.これは,出力コネクタにパラレルATAケーブルを流用している都合です.

マイコン ATmega1284P
D-FF TC74VHC574F
バッファ TD62783APG

f:id:sosoru_m:20151018143134p:plain

ソフトウェアについて

マイコンは20MHzで駆動しており,通信基板とSPIで会話しつつ,ソフトPWMをこなします.下にコードを貼ります.(いま見ると命名がひどいなぁ)

#include "avr_base.hpp"
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>
#include <Timer.h>
#include <string.h>
#include <tus.h>
#include <led_packet.h>
using namespace AVRCpp::Timer;

#define PORT_DATA			PORTA
#define PORT_CLK			PORTD
#define PORT_ENABLE			PORTC
#define PORT_ENABLE_NUM		0

#define LED_COUNT			30

LedState g_States[LED_COUNT];
DeviceID g_myDevID;

uint8_t led_count_remains[LED_COUNT];
uint8_t led_count_init[LED_COUNT];

#define APPLY_TO_LED(clk,ind) \
if(led_count_remains[(clk+1)*8-ind-1] >0){ \
	sbi(led_signal_buffer, ind); \
	led_count_remains[(clk+1)*8-ind-1]--; \
}else{ \
	cbi(led_signal_buffer, ind);} 
				
ISR(TIMER1_COMPA_vect)
{
	tus_spi_process_packets();
}

void device_init()
{
	DDRA = 0xFF;
	DDRD = 0xFF;
	DDRC = 0xFF;
	
	cbi(PORT_ENABLE, PORT_ENABLE_NUM); // enable D-flipflops
		
	memset(led_count_init, 0, sizeof(led_count_init));
	memset(led_count_remains, 0x00, sizeof(led_count_remains));
}

void init_led_status()
{
	uint8_t cache = SREG;
	cli();
	memcpy(led_count_remains, led_count_init, sizeof(led_count_init));
	SREG = cache;
}

void apply_led_status()
{
	uint8_t led_signal_buffer;
	
	APPLY_TO_LED(0,0);
	APPLY_TO_LED(0,1);
	APPLY_TO_LED(0,2);
	APPLY_TO_LED(0,3);
	APPLY_TO_LED(0,4);
	APPLY_TO_LED(0,5);
	APPLY_TO_LED(0,6);
	APPLY_TO_LED(0,7);
	PORT_DATA = led_signal_buffer;
	PORT_CLK = 1<<0;
	
	APPLY_TO_LED(1,0);
	APPLY_TO_LED(1,1);
	APPLY_TO_LED(1,2);
	APPLY_TO_LED(1,3);
	APPLY_TO_LED(1,4);
	APPLY_TO_LED(1,5);
	APPLY_TO_LED(1,6);
	APPLY_TO_LED(1,7);
	PORT_DATA = led_signal_buffer;
	PORT_CLK = 1<<1;
	
	APPLY_TO_LED(2,0);
	APPLY_TO_LED(2,1);
	APPLY_TO_LED(2,2);
	APPLY_TO_LED(2,3);
	APPLY_TO_LED(2,4);
	APPLY_TO_LED(2,5);
	APPLY_TO_LED(2,6);
	APPLY_TO_LED(2,7);
	PORT_DATA = led_signal_buffer;
	PORT_CLK = 1<<2;
	
	//APPLY_TO_LED(3,0);
	//APPLY_TO_LED(3,1);
	APPLY_TO_LED(3,2);
	APPLY_TO_LED(3,3);
	APPLY_TO_LED(3,4);
	APPLY_TO_LED(3,5);
	APPLY_TO_LED(3,6);
	APPLY_TO_LED(3,7);
	PORT_DATA = led_signal_buffer;
	PORT_CLK = 1<<3;
	PORT_CLK = 0;
}

bool ProcessLedPacket(LedState *pstate)
{
	if(pstate->Base.ModuleType != MODULETYPE_LED
		&& pstate->Base.InternalAddr > LED_COUNT
		&& pstate->Base.InternalAddr == 0)
		return false;
	
	led_count_init[pstate->Base.InternalAddr-1] = pstate->DutyValue;
	
	return true;
}

void spi_received(args_received *e)
{
	g_myDevID.raw  = e->pdstId->raw;

	ProcessLedPacket((LedState*)e->ppack);
}

int main(void)
{	
	uint8_t i;
	MCUCR = 0b01100000; //todo: turn off bods
	MCUSR = 0; // Do not omit to clear this resistor, otherwise suffer a terrible reseting cause.
	
	tus_spi_init();
	tus_spi_set_handler(spi_received);
	
	device_init();
	TimerCounter1::SetUp(NoPrescaleB, Normal16, NormalPortOperationA, NormalPortOperationB, Off, Fall);
	TimerCounter1::CompareMatchAInterrupt::Enable();

    while(1)
    {	
		init_led_status();
		for(i=0; i<128; ++i)
		{
			apply_led_status();
			_delay_us(100);
		}
	}
}

通信基板との会話

別ファイルにSPI割り込みの関数が定義されていて,データの送受信を行っています.
タイマ割り込みでtus_spi_process_packets()が呼び出されており,定期的に受信したデータの処理を行っています.この関数は,事前にtus_spi_handler()で指定された関数を間接的に呼び出し,具体的な処理を委ねています.このモジュールではspi_received()が設定されています.spi_received()は受信したデータを実際に反映していて,LEDのDuty比設定を更新しています.

ソフトPWM

led_count_initに設定するDuty比を書いておいて,周期の最初,led_count_remainsにled_count_initをコピーします.
ループ毎にled_count_remainsをデクリメントし,0になるまでオンにしておきます.この処理に相当するのがapply_led_status()です.
これが最も時間的な制約を受けそうですが,コンパイル時に-O3で最適化を書けた場合で,最悪でも大体200クロックで抜けるようです.これは,クロック周波数が20MHzだとすると,10usec程度で処理か終わる見込みです.
直後の100usecのdelayを考えると,1周期は約14msecぐらいで回り,PWM周波数は71Hz程度と予測できます.SPIの割り込み等を考えると,もう少し遅くなっているでしょう.

おわり

あとで写真を撮って貼っておきたい
本当はポイントモジュールが先だったけど,設計がちょっと疑問なので実測してから載せる予定