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

###概要
Smile Function Decoderを使ってアクセサリデコーダタイプのサーボモータデコーダを考えました。
***
###動作
・初期アドレスは１
***
###今後
・サーボモータがONまたはOFFまで達したら、PWM波形を0Vに停止させた方が良いのでしょうか。
→出しぱなしにすることにした。
・CV65とCV66の移行時間を設定してもちゃんと反映されない。
　フラッシュの空きがなくシリアルポートデバックができないので難航中。
→#include <SoftwareSerial.h> をインクルードして色々機能を削ってデバックしました。
・DCC Manufacture ID 166番が取得できましたのでソースファイルに反映
・最終値をeep-romに書き込み、次起動時にコマンドステーションから送られてくるアクセサリ命令を確認して、
動作させるようにしました。
・ios版のArduinoIDEは1.8.5を使わないと、ATtiny85に書けなくなった。(2019/3/10現在)
・Ａｔｔｉｎｙ８５の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

```c++

// 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;
  }   
}