LoginSignup
1
1

More than 5 years have passed since last update.

DCC サーボモータアクセサリデコーダ

Last updated at Posted at 2018-11-18

概要

Smile Function Decoderを使ってアクセサリデコーダタイプのサーボモータデコーダを考えました。


動作

・初期アドレスは1


今後

・サーボモータがONまたはOFFまで達したら、PWM波形を0Vに停止させた方が良いのでしょうか。
→出しぱなしにすることにした。
・CV65とCV66の移行時間を設定してもちゃんと反映されない。
 フラッシュの空きがなくシリアルポートデバックができないので難航中。
→#include をインクルードして色々機能を削ってデバックしました。
・DCC Manufacture ID 166番が取得できましたのでソースファイルに反映
・最終値をeep-romに書き込み、次起動時にコマンドステーションから送られてくるアクセサリ命令を確認して、
動作させるようにしました。
・ios版のArduinoIDEは1.8.5を使わないと、ATtiny85に書けなくなった。(2019/3/10現在)
・Attiny85の8bitタイマカウンタを使用して、サーボモータのパルス幅 500usと2400usを作っているので、
28ステップになってしまっています。本格的にやるのであれば、16bitタイプにベースを変えないと
ギクシャクするのでいまいちです。(ファンクションタイプも同じ)


リンク

https://twitter.com/masashi_214
http://ayabu.blog.shinobi.jp/
http://dcc.client.jp/
http://www007.upp.so-net.ne.jp/nagoden/

ソースファイルはグーグルドライブで公開
https://drive.google.com/drive/folders/1dBlOljjjMt6QHX4UHgOAKwrrREw974yC?usp=sharing


// DCC Accessory Decoder for Smile Servo Decoder
// Base Decoder AYA002
// 
// Attiny85 16MHz(PLL) BOD.Enabled 1.8v LTO.dis
//
// DCC電子工作連合
// https://desktopstation.net/tmi/
//
// DCC館
// http://dcc.client.jp/
// http://ayabu.blog.shinobi.jp/
// https://twitter.com/masashi_214
//

#include "NmraDcc.h"
#include <avr/eeprom.h>

//各種設定、宣言
#define DECODER_ADDRESS 1
#define DCC_ACK_PIN 0   // Atiny85 PB0(5pin) if defined enables the ACK pin functionality. Comment out to disable.
//                      // Atiny85 DCCin(7pin)
#define O1 0            // Atiny85 PB0(5pin)
#define O2 1            // Atiny85 PB1(6pin) analogwrite
#define O3 3            // Atint85 PB3(2pin)
#define O4 4            // Atiny85 PB4(3pin) analogwrite Servo用

#define ON 1
#define OFF 0
#define UP 1
#define DOWN 0

// ファンクション CV変換テーブル
#define CV_zeroDeg 60       // 0deg時のPWMの値
#define CV_ninetyDeg 61     // 90deg時のPWMの値
#define CV_onDeg 62         // on時の角度
#define CV_offDeg 63        // off時の角度
#define CV_initDeg 64       // 起動時の角度
#define CV_onSpeed 65       // off->onに移行する時間
#define CV_offSpeed 66      // on->offに移行する時間
#define CV_sdir 67          // 最後のdir保持用
#define CV_dummy 68         // dummy
#define MAN_ID_NUMBER 166   // Manufacture ID //
#define MAN_VER_NUMBER  01  // Release Ver CV07 //

//Task Schedule
unsigned long gPreviousL5 = 0;

uint8_t zeroDeg = 0;        // 0degのPWM値
uint8_t ninetyDeg = 0;      // 90degのPWM値 ※ HK-5320は0-60deg DM-S0025は0-180deg
uint8_t onDeg = 0;          // ON時の角度
uint8_t offDeg = 0;         // OFF時の角度
uint8_t initDeg = 0;        // 電源切る前の角度
uint8_t onSpeed = 0;        // OFF->ONのスピード
uint8_t offSpeed = 0;       // ON->OFFのスピード
uint8_t gDir = 0;           // ON/OFF 
uint8_t sdir = 0;           // gDirの最新値保存用
uint8_t Ndas = 0;

NmraDcc  Dcc ;
DCC_MSG  Packet ;

//Internal variables and other.
#if defined(DCC_ACK_PIN)
const int DccAckPin = DCC_ACK_PIN ;
#endif

struct CVPair
{
  uint16_t  CV;
  uint8_t   Value;
};

CVPair FactoryDefaultCVs [] =
{
  {CV_ACCESSORY_DECODER_ADDRESS_LSB, 1},
  {CV_ACCESSORY_DECODER_ADDRESS_MSB, 0},
  {CV_29_CONFIG, 0b10000000},             // CV29 Software sw CV29=128 アクセサリデコーダ
  {CV_zeroDeg ,8},                        // CV60 8で約500us
  {CV_ninetyDeg ,36},                     // CV61 36で約2400us DM-S0025は36以上に設定すると動きがおかしくなる
  {CV_onDeg ,180},                        // CV62 on時の角度
  {CV_offDeg  ,0},                        // CV63 off時の角度
  {CV_initDeg ,0},                        // CV64 電源切る前の角度
  {CV_onSpeed, 20},                       // CV65 20で2000msec
  {CV_offSpeed, 20},                      // CV66 20で2000msec
  {CV_sdir , 0},                          // CV67 0
  {CV_dummy,0},
};

uint16_t gAccessoryAddress = 1;
uint8_t FactoryDefaultCVIndex = sizeof(FactoryDefaultCVs) / sizeof(CVPair);

void(* resetFunc) (void) = 0;  //declare reset function at address 0

//------------------------------------------------------------------
// notifyCVResetFactoryDefault()
// CV値を工場出荷状態に設定
//------------------------------------------------------------------
void notifyCVResetFactoryDefault()
{
  //When anything is writen to CV8 reset to defaults.
  resetCVToDefault();
  //Serial.println("Resetting...");
  delay(1000);  //typical CV programming sends the same command multiple times - specially since we dont ACK. so ignore them by delaying
  resetFunc();
}

//------------------------------------------------------------------
// CVをデフォルトにリセット
// Serial.println("CVs being reset to factory defaults");
//------------------------------------------------------------------
void resetCVToDefault()
{
  for (int j = 0; j < FactoryDefaultCVIndex; j++ ) {
    Dcc.setCV( FactoryDefaultCVs[j].CV, FactoryDefaultCVs[j].Value);
  }
}

//------------------------------------------------------------------
// CV Ack
// Smile Function Decoder は未対応
// This function is called by the NmraDcc library when a DCC ACK needs to be sent
// Calling this function should cause an increased 60ma current drain on the power supply for 6ms to ACK a CV Read 
//------------------------------------------------------------------
void notifyCVAck(void)
{
// DCCservo decoderでは、これをやると、CV値がうまく書けない。
//Serial.println("notifyCVAck");
#if 0 
  digitalWrite(O1,HIGH);
  digitalWrite(O2,HIGH);
  digitalWrite(O3,HIGH);
  digitalWrite(O4,HIGH);
  delay( 6 );
  digitalWrite(O4,LOW);
  digitalWrite(O3,LOW);
  digitalWrite(O2,LOW);
  digitalWrite(O1,LOW);
#endif
  MOTOR_Ack();
}


void MOTOR_Ack(void)
{

  analogWrite(O4, 10);

  delay( 6 );  

  analogWrite(O4, 0);
}

//------------------------------------------------------------------
// アクセサリアドレス取得
//------------------------------------------------------------------
uint16_t getMyAddr_Acc(void)
{
  uint16_t  Addr ;
  uint8_t   CV29Value ;

  CV29Value = Dcc.getCV( CV_29_CONFIG ) ;

  if( CV29Value & 0b10000000 ) { // Accessory Decoder? 
    Addr = ( Dcc.getCV( CV_ACCESSORY_DECODER_ADDRESS_MSB ) << 8 ) | Dcc.getCV( CV_ACCESSORY_DECODER_ADDRESS_LSB ) ;
  }
  else   // Multi-Function Decoder?
  {
    if( CV29Value & 0b00100000 )  // Two Byte Address?
      Addr = ( Dcc.getCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB ) << 8 ) | Dcc.getCV( CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB ) ;

    else
      Addr = Dcc.getCV( 1 ) ;
  }

  return Addr ;
}

//------------------------------------------------------------------
// This function is called whenever a DCC Signal Aspect Packet is receivedz
//------------------------------------------------------------------
void notifyDccSigState( uint16_t Addr, uint8_t OutputIndex, uint8_t State)
{
}

//------------------------------------------------------------------
// Arduino固有の関数 setup() :初期設定
//------------------------------------------------------------------
void setup()
{
  // Configure the DCC CV Programing ACK pin for an output
  pinMode( DccAckPin, OUTPUT );

//  TCCR1 = 0<<CTC1 | 0<<PWM1A | 0<<COM1A0 | 1<<CS10;
  pinMode(O1, OUTPUT);
  pinMode(O2, OUTPUT);
  pinMode(O3, OUTPUT);
  pinMode(O4, OUTPUT);
//  digitalWrite(O4 ,HIGH);   // 起動時にサーボが0deg方向に最大まで行かないように

  // Setup which External Interrupt, the Pin it's associated with that we're using and enable the Pull-Up 
  Dcc.pin(0, 2, 0);

  // Call the main DCC Init function to enable the DCC Receiver
  Dcc.init( MAN_ID_NUMBER, MAN_VER_NUMBER, FLAGS_OUTPUT_ADDRESS_MODE | FLAGS_DCC_ACCESSORY_DECODER, 0 );

  //アクセサリアドレス(下位2bitを考慮)の先修得
  gAccessoryAddress = getMyAddr_Acc();

  //Init CVs
  zeroDeg = Dcc.getCV( CV_zeroDeg );
  ninetyDeg = Dcc.getCV( CV_ninetyDeg );
  onDeg = Dcc.getCV( CV_onDeg );
  offDeg = Dcc.getCV( CV_offDeg );
  initDeg = Dcc.getCV( CV_initDeg );
  onSpeed = Dcc.getCV( CV_onSpeed );  
  offSpeed = Dcc.getCV( CV_offSpeed );
  sdir = Dcc.getCV( CV_sdir );

  GTCCR = 1 << PWM1B | 2 << COM1B0;
  TCCR1 = 11 << CS10;
  gPreviousL5 = millis();
}

//---------------------------------------------------------------------
// Arduino Main Loop
//---------------------------------------------------------------------
void loop()
{
  // You MUST call the NmraDcc.process() method frequently from the Arduino loop() function for correct library operation
  Dcc.process();


  if( FactoryDefaultCVIndex && Dcc.isSetCVReady())
  {
    FactoryDefaultCVIndex--; // Decrement first as initially it is the size of the array 
    Dcc.setCV( FactoryDefaultCVs[FactoryDefaultCVIndex].CV, FactoryDefaultCVs[FactoryDefaultCVIndex].Value);
  }

  if ( (millis() - gPreviousL5) >= 10) // 100:100msec  10:10msec  Servo Motor decoder は 10msecにしてみる。
  {
    ServoControl();
    gPreviousL5 = millis();
  }
}

//------------------------------------------------------------------
// アクセサリ命令のon/offの処理
// This function is called whenever a normal DCC Turnout Packet is received
//------------------------------------------------------------------
void notifyDccAccState( uint16_t Addr, uint16_t BoardAddr, uint8_t OutputAddr, uint8_t State)
{
  uint16_t aAccAdress = Addr;

  Ndas = 1; // コマンドステーション起動時にここを通ったかのフラグ

//       digitalWrite(O1,HIGH);     
  //アドレスチェック(CVから得た11bit分の信号を比較)
  if( gAccessoryAddress != aAccAdress)
  {
    return;
  }
  gDir = (OutputAddr & 0b001);
}

//---------------------------------------------------------------------
// Servo control Task (100Hz:100ms)
//---------------------------------------------------------------------
void ServoControl()
{
  enum{
      ST_INIT = 0,
      ST_STANDABY,
      ST_IDLE,
      ST_ON,
      ST_ON_RUN,
      ST_OFF,
      ST_OFF_RUN,
  };

  static char state = ST_INIT;        // ステート
  static float delt_deg = 0;          // 10ms辺りの変化量
  static char updown_flg = 0;         // 0:up 1:down
  static float nowDeg = 0;

  switch(state){
      case ST_INIT:
        if(Ndas == 1)
            state = ST_STANDABY;
        break;
      case ST_STANDABY:               // 起動時一回だけの処理 
        if(gDir == sdir ){            // 前回最後のSTR/DIVが同じ?
          if(gDir == 0){              // 分岐 t DIV ?
            nowDeg = offDeg;     
          } else {                    // 直進 c STR |
            nowDeg = onDeg;  
          }
          state = ST_IDLE;
          break;
        } else {
          if(sdir == 1 and gDir == 0){
              state = ST_OFF;             
          } else {
            state = ST_ON;
          }
        }
        break;
      case ST_IDLE: // 1 
            if(gDir == 0 ){           // Servo O4:OFF
              if(nowDeg == offDeg){   // 最終値まで行っていたら抜ける
                state = ST_IDLE;
                return;
              }
              state = ST_OFF;
            } else if(gDir == 1 ){    // Servo O4:ON
              if(nowDeg == onDeg){    // 最終値まで行っていたら抜ける
                state = ST_IDLE;
                return;
              }
              state = ST_ON;
            }
            break;

      case ST_ON: //11: // ON処理
            if(onDeg - offDeg > 0){  // 上昇か、下降か確認
              updown_flg = UP;       // 上昇
              delt_deg = (float)abs(onDeg - offDeg) / onSpeed / 10; // offDegからonDegまでの移動量を算出
            } else {
              updown_flg = DOWN;       // 下降
              delt_deg = (float)abs(onDeg - offDeg) / offSpeed / 10; // offDegからonDegまでの移動量を算出
            }

            nowDeg = offDeg;        // 現在の角度を導入
            analogWrite(O4, map(nowDeg,0,180, zeroDeg, ninetyDeg)); 
            state = ST_ON_RUN;
            break;

      case ST_ON_RUN: // 12
              if(updown_flg == UP) {
                nowDeg = nowDeg + delt_deg;       // 上昇
                if(nowDeg > onDeg){
                  nowDeg = onDeg;
                  analogWrite(O4, map(nowDeg,0,180, zeroDeg, ninetyDeg));
                  Dcc.setCV(CV_sdir,gDir);        // 最終値のアクセサリ番号をCV_sdirに書き込み
                  digitalWrite(O1, LOW);
                  digitalWrite(O2, HIGH);
                  state = ST_IDLE;                  
                } else {
                  analogWrite(O4, map(nowDeg,0,180, zeroDeg, ninetyDeg));               
                  state = ST_ON_RUN;                  
                }
              } else {
                nowDeg = nowDeg - delt_deg;       // 下降   
               if(nowDeg < onDeg){
                  nowDeg = onDeg;
                  analogWrite(O4, map(nowDeg,0,180, zeroDeg, ninetyDeg));
                  Dcc.setCV(CV_sdir,gDir);        // 最終値のアクセサリ番号をCV_sdirに書き込み
                  digitalWrite(O1, HIGH);
                  digitalWrite(O2, LOW);
                  state = ST_IDLE;   
                } else {
                  analogWrite(O4, map(nowDeg,0,180, zeroDeg, ninetyDeg));               
                 state = ST_ON_RUN;                    
               }
              }
            break;

      case ST_OFF: //21:  // ON->OFF処理
            if(onDeg - offDeg > 0){  // 上昇か、下降か確認
              updown_flg = DOWN;       // 下昇
              delt_deg = (float)abs(onDeg - offDeg) / offSpeed / 10; // offDegからonDegまでの移動量を算出
            } else {
              updown_flg = UP;       // 上降
              delt_deg = (float)abs(onDeg - offDeg) / onSpeed / 10; // offDegからonDegまでの移動量を算出
            }
            nowDeg = onDeg;        // 現在の角度を導入
            analogWrite(O4, map(nowDeg,0,180, zeroDeg, ninetyDeg)); 
            state = ST_OFF_RUN;
            break;

      case ST_OFF_RUN: //22
              if(updown_flg == UP) {
                nowDeg = nowDeg + delt_deg;       // 上昇
                if(nowDeg > offDeg){
                  nowDeg = offDeg;
                  analogWrite(O4, map(nowDeg,0,180, zeroDeg, ninetyDeg));
                  Dcc.setCV(CV_sdir,gDir);        // 最終値のアクセサリ番号をCV_sdirに書き込み
                  digitalWrite(O1, LOW);
                  digitalWrite(O2, HIGH);
                  state = ST_IDLE;                  
                } else {
                  analogWrite(O4, map(nowDeg,0,180, zeroDeg, ninetyDeg));
                  state = ST_OFF_RUN;                  
                }
              } else {
                nowDeg = nowDeg - delt_deg;       // 下降
                if(nowDeg < offDeg){
                  nowDeg = offDeg;
                  analogWrite(O4, map(nowDeg,0,180, zeroDeg, ninetyDeg));    
                  Dcc.setCV(CV_sdir,gDir);        // 最終値のアクセサリ番号をCV_sdirに書き込み
                  digitalWrite(O1, HIGH);
                  digitalWrite(O2, LOW);
                  state = ST_IDLE;   
                } else {
                  analogWrite(O4, map(nowDeg,0,180, zeroDeg, ninetyDeg));               
                  state = ST_OFF_RUN;                    
                }
              }
            break;

      default:
            break;
  }   
}
1
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1