###概要
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;
}
}