TOPへ戻る


LocoNet/DCC Throttletの紹介

LocoNetのI/Fを持つスロットルを作ってみました。DCS50K等のコマンドステーションに備わっているLocoNetのI/Fに
接続するだけで、スロットルとして動作します。
■部品面 ■はんだ面 ■コネクタ側




LocoNet/DCC Throttleの特徴

・LocoNet/DCC ThrottleはArduino というオープンハード、オープンソフトウエアを使用して作られております。
・LocoNet/DCC Throttleには大きく3つのモードがあります。(1,LocoNet Throttleモード
 2,DCCコマンドステーションThrottleモード 3,AnalogThrottleモード)

■LocoNet Throttleモード
・LocoNet用のThrottleとして機能します。
・LOCOアドレスは1〜9983まで対応(2桁と4桁対応)
・ファンクッションアドレスは1〜16まで対応
・アクセサリアドレスは1〜999まで対応。
 ボタンに対応したアドレスは1〜20まで
 ※上記アドレス以外はノブで設定します。
・速度は0〜128STEP(28STEPは今後対応)

■DCCコマンドステーションThrottleモード
・DCCのコマンドステーションとして単独で機能します。
・LOCOアドレスは1〜9983まで対応(2桁と4桁対応)
・ファンクッションアドレスは1〜16まで対応
・アクセサリアドレスは1〜999まで対応。
 ボタンに対応したアドレスは1〜20まで
 ※上記アドレス以外はノブで設定します。
・速度は0〜128STEP(28STEPは今後対応)

■AnalogThrottleモード
・PWM出力タイプのアナログコントローラとして単独で機能します。

■ハードウエア
・CPUはATmega328を使用(Flash32kB,SRAM2kB,動作周波数16MHz
・液晶は128x64のOLEDを使用
・スロットルはABZ方式のプッシュ付きロータリーエンコーダ
・SHIFTボタン、ファンクッションボタン5個、リセットスイッチ
・PWMドライバ TOSHIBA TB6643相当品
・電源DC12〜20V
・POWER LED、TRACK LED、SHIFT LED
・LocoNetコネクタ(RJ11 6pin)
・DCCコネクタ(KATO CONNECTOR)
・DCジャック


写真

動画に使用したレイアウトです
・外周 E7系新幹線のみ(DCCアドレス3)
・内周と外周はEF200(DCCアドレス1)、EF65-500(DCCアドレス2)



KATO DCS50KとDCC速度計をセット。


起動画面
現在の設定状況を表示しています。この状態から設定変更画面に遷移できます(まだ作っていません)


速度設定画面
単位がkm/hになっていますが、スロット値そのままです


ファンクッション画面、速度画面からSHIFTキークリックで遷移します。
(クリックで行を選択、ダブルクリックで速度画面に戻ります)


ポイント(アクセサリ?)設定画面、速度画面からSHIFTキーのダブルクリックで遷移します
(クリックで行選択、ダブルクリックで速度画面に戻ります)


画面遷移は、こんな感じの状態遷移表を元に作成しました。
縦軸にイベント、横軸はステート(状態)を表しています。
画面遷移の状態がおかしい時はこの表をみるとすぐに分かります。


動画



LocoNet/DCC Throttleの回路図

回路図をダウンロード

※周知の問題(errata)
LocoNetの送受信回路が間違っていました。パターンカット2箇所、ストラップ2本追加です。回路図は修正済み。
LEDが明るすぎ。

修正箇所

・トランジスタのE,C,Bのフットプリントと回路図の定義が合っていなかったので、45deg回転させて実装。
LocoNetの送受信回路の回路が間違っていたのと、Atmega328への接続を修正するために2箇所パターンカット。


ラッピング線を直角水平になるように配線して


マジックハンダ(ハックルー)で固定して、最後の難関 Atmega328の端子へのハンダ付け。


部品表

部品表


LocoNet/DCC Throttleのスケッチ

スケッチをダウンロード

以下のライブラリを使用しいます。
LocoNet Library
u8glib

■プログラムについて
Arduinoを司る、setup()、loop()をベースに作られています。
各種制御が滞らないようにステートマシンを採用した構成で作られています。
(数回関数が呼ばれると処理が完了します)
エンコーダー、キー処理は、変化があるとメッセージに載せて、エンコーダ処理、キー処理を行うようにしています
※メッセージといっても、変数に代入しているだけです(^^;
void loop() {
 
  EncoderState();         // エンコーダー処理
  DirKeyState();          // DIRキー処理
  ShiftKeyState2();       // SHIFTキー処理  
  FunctionKeyState();     // ファンクッションキー処理
  DisplayState();         // 画面処理
  adrState(0);            // アドレス取得処理
  pointState(0);          // ポイント処理
    
  // Check for any received LocoNet packets
  LnPacket = LocoNet.receive() ;
  if (LnPacket)  {
    if (!LocoNet.processSwitchSensorMessage(LnPacket))
      Throttle.processMessage(LnPacket) ; 
  }
  
  if(isTime(&LastThrottleTimerTick, 100)) {       // Locoには100ms周期でアクセスが必要だそうです。
    Throttle.process100msActions(); 
    if (Throttle.getState() != TH_ST_IN_USE){     //no Slot - Blink LED F0
      digitalWrite(Led_Track, HIGH);
    }else
       digitalWrite(Led_Track, LOW);     
    if (Throttle.getState() == TH_ST_SLOT_MOVE){    //Blink off
      digitalWrite(Led_Track, HIGH);
    }
  }
}


■u8glibについて

u8glibは最新版であるu8g2libはSRAMを使いすぎるため、使用していません。

u8glibとu8g2libのSRAM消費の比較、OLEDのSSD1306 128x64を使っています。


u8glibが
最大2,048バイトのRAMのうち、グローバル変数が272バイト(13%)を使っていて、ローカル変数で1,776バイト使うことができます。
に対してu8g2libは
最大2,048バイトのRAMのうち、グローバル変数が1,518バイト(74%)を使っていて、ローカル変数で530バイト使うことができます。
なので、ここまでSRAMが食われていると、u8g2libではLocoNetライブラリを組み込むことが出来ませんでした。

■u8glibによるカーソルブリンクについて
今回アンダーバーカーソルとキャラクタ反転カーソルを作りました。

カーソル点滅周期に合わせて、cursor()関数をコールすることでカーソルブリンクが出来ています。

引数には、表示するx,y、点滅モード、カーソル位置、文字列を渡します。

キャラクタ反転は、u8g.drawBox()で、下地の白い四角を表示し、u8g.setDefaultBackgroundColor()で文字を黒にして、
u8g.drawStr()で文字をプロット、u8g.setDefaultForegroundColor()で文字を白に戻しています。

ブリンク中は常にu8glibの描画イベントを掛けているので作りが悪いのでブリンク処理中はかなり表示処理に取られています。
//--------------------------------------------------------------------------------------------------
// cursor
// カーソルブリンク処理
// mode 0:アンダーラインタイプ
// mode 1:1文字選択タイプ
//--------------------------------------------------------------------------------------------------
void cursor(char x,char y, char mode,char pos, char *buf){
  char pbuf[5];

  switch(mode){
    case 0:
            u8g.drawStr( 40, 24, buf);
            u8g.drawBox( 40+pos*19, y, 20, 2);  
            break;
    case 1:
            for(int i=0;i<=4;i++){
              pbuf[0]=buf[i];pbuf[1]=0x00;
              if(i==pos){ // drawBox -> setDefaultBackgroundColor -> drawStr -> setDefaultForegroundColor この順番が大事
                u8g.drawBox( 40+pos*19, y-25, 20, 25);
                u8g.setDefaultBackgroundColor();
                u8g.drawStr( 40+i*19, 24, pbuf);
                u8g.setDefaultForegroundColor();    
              } else
                u8g.drawStr( 40+i*19, 24, pbuf);
            }
            break;
    default:
            break;
  }
}

■状態遷移表のよるステートマシンについて

まずは、ステート(状態)を考え、enumで定義します。
画面のステートはIniSc:Initialize Screen(初期設定)、IniEvSc:Initialize Event Screen(イベントで処理する画面)
という感じに名付けています。
enumで定義しておくと途中に画面を増やしたいときに数字を振りなおさなくて良いので便利です。

// 画面ステート定義
enum {
  IniSc = 0,
  IniEvSc,
  AddressSc,
  AddressPosSc,       // アドレス設定の桁設定
  AddressNumSc,
  AddressEvSc,        // アドレス設定の値設定
  SpeedSc,
  SpeedEvSc,
  EmgSc,
  EmgEvSc,
  FunctionSc,
  FunctionEvSc,
  PointSc,
  PointEvSc,          // Point   
} ScreenState;

基本的にステートマシンの処理はswitch caseを使用します。
イベントが発生したり、次の処理を行うときに、ScreenState変数を操作して、次の画面へ設定します。

u8glibによる描画もかなり時間が掛かりますので、表示処理部をステートマシンにして、
u8gState = DrawingStart;というイベントを与えて、数回ループからアクセスされて描画が終わります。

// --------------------------------------------------------------------------------
// 画面表示ステートマシン
// --------------------------------------------------------------------------------
void DisplayState(){
    static char buf[5];

    switch(ScreenState){
      //----------- 初期画面
      case IniSc:
            TimerTick = millis();
            ScreenState = IniEvSc;
            break;
      case IniEvSc:
            if(u8gState==Idle)
              u8gState = DrawingStart;          // 表示ステート:描画開始
            if( millis()-TimerTick > 2000 ){    // 1000=1sec
              ScreenState = AddressEvSc;
            }
            break;
                    
      //----------- アドレス設定
      case AddressSc :
            u8gState = DrawingStart;      // 表示ステート:描画開始
            ScreenState = AddressPosSc;
            break;

■ボタンのクリック、ダブルクリック、長押しについて

以前、考えた方法を使っています。
長押しとの組み合わせがうまくいかない場合があるのでちょっと考え直す必要があります。


■LocoNetlibについて

DCS50Kからスロット取得して取得できたら使えるようになるという構造です
ちょっと分からないところもあったりして、今はコメントアウトしてごまかしています(^^;

■SRAMの節約について

文字列はFlashからSRAMに展開されるので、SRAMが消費します。
Serial.println("hello, 2in1 LocoNET/DCC Throttle AYA006-1");
F()でくくるとSRAMに展開されませんので、お勧めです。
Serial.println(F("hello, 2in1 LocoNET/DCC Throttle AYA006-1"));


リンク

前作ったプロトタイプ
Arduino LocoNet Throttle
このサイトの回路とスケッチを参考

桂庵さんのLocoNet Personal Editionの解説
すばらしい資料です。

2009/09/12 ポイントの省配線システム(ポイント制御パネル)LocoPointを作る

あやのブログ

DesktopStation/DCC電子工作連合



2017/05/06 1版