SMA12 - 17 Channel Configurable Multifunction $5 DCC Decoder For Servos

geoffb's picture

One of my earlier entries:  SMA10 – Build a 17-Function DCC Decoder for about $5 ( http://model-railroad-hobbyist.com/node/19070 ) generated a considerable amount of interest regarding the possible enhancement for controlling Servomotors (Servos). This is my next version of a 17 Channel Multifunction DCC Decoder based on a low cost $2.56 Arduino Pro Mini. This version supports configuring each of the 17 function pins for On/Off (LED/TTL) Control, or Configurable Blinking Control, or Configurable Servo Control, or Configurable Pairs Blinking Control. Yes, that does mean it can support 17 servos, each with rate, start point, and end point setting via DCC CV’s, per pin, as well as new LED configurable functions. Read On. Additional doc found here: http://model-railroad-hobbyist.com/node/19775 There is another decoder version added herein. Look for "New Decoder Version to Control Lighting Groups" in Page 12 of the Comments: http://model-railroad-hobbyist.com/node/19446?page=11   The most recent Update can be found here: SMA20 New Low Cost 17 Channel DCC Decoders with PC Boards & Dual Motor, LED, & Servo Control    http://model-railroad-hobbyist.com/node/24316

Comments

geoffb's picture

@Roberto re: Questions

Hi Roberto,

If I customize the sketch and change its name, it forces me to create a new directory. Is Arduino loosing some connection between the sketch and the NmraDcc.h and SoftwareServo.h? Do I have to teach him find the new path to the libraries? I've seen that there is a specific command to include the libraries. Is it sufficient to simply reposition the sketc or do I have to manually make the links again?

I assume you are pullng the sketch from the NmraDcc examples list. First, anytime you are going to modify the sketch, save it under a new name in your Documents\Arduino\ folder before you download it. If you don't the IDE may try to "help" you if you exit and then restore your windows -- it will not retain your changes.The entire NmraDcc folder should be downloaded and placed in the Documents\Arduino\libraries\ folder-- nowhere else. Since you are having difficulties, if asked to atuo update your libraries say NO. Also if you have used auto update with the later Arduino releases, delete the NmraDcc lib, download using the link in SMA20  and put in in the libraries folder. If you rename and save different versions, you do not copy and make multiple libraries -- it is a sure way to mess things up!

The programming of Pro Mini is a little tweaky. If I simply send the sketch to the Arduino, it stays forever transmitting until a timeout happens. I've learned that depending on the used USB-TTL interface (mine is a 4 pins PL2303), it's required to push the Arduino reset button in a specific moment just before the uploading takes place. I wonder if the exact moment in which i press reset is so critical to invalid the upload, although no errors appear and it says transfer complete at the end.

This sounds incredibly unreliable, and I would strongly recommend you get an FTDI USB serial cable from a known reliable source. I have never used a PL2303 interface, so I have nothing else to say about it.

Between  the first upload and the second upload with removed //, is it good to reset the Arduino and the USB -TTL interface by unplugging and replugging? I've experience a few times (2 or 3) some random transmission errors in the second attempt if I do the uploads one immediately after the other.

If you had a reliable interface, the interface does an automatic reset as part of the upload process. The intermittent problems and random transmission errors all sound like cable/interface problems, and could easily account for bad data loaded into your Arduinos. Further, unplugging and re-plugging USB interfaces into Windows machines on a regular basis is something I would go out of my way to avoid, based on my time spent with MS operating systems. This is asking for trouble.

Is it possible to read the CV values?

No, not implemented.

Secondary Comments:

I change the decoder addresses and CV's all the time, usually in the IDE editor, but also with a Digitrax system. It works consistently. That said I do not use the NCE system, but many, many modelers do, and no NCE specific problems with the decoders have been reported. I have heard about NCE issues with short vs long addresses, but have never paid much attention to it.

Have fun!  smiley

Best regards,

Geoff

 

Still not working

Hi Geoff,

I've religiously read and reread your last post to e sure that I understood the directions.

So, what I did was to take the version Accessory_15Servos_2LEDs (which is the most similar to a full 17Servos I want to configure), change its name to a custom name and save it in my Arduino folder, without making any change to the sketch.  Uploaded to Arduino twice to check once again if it works. It does.

Then I edited the programming address and the decoder address to be respectively 19 and 20. Edited the CV 105-109  and 110-114 to have two servos and complete the full 16 servos. Only this. Saved again with the same name, so if the previous untouch version worked, this one should work too.

Uploaded twice to Arduino, second time removing the // on #define DECODER_LOADED.

This version doesn't work. THis is making me mad. I cannot explain it.

Here is the sketch, with the altered values. Could you please have a look and check if I made bollocks with it?

// Production 17 Function DCC Decoder
// Version 5.1  Geoff Bunza 2014,2015,2016
// NO LONGER REQUIRES modified software servo Lib
// Software restructuring mods added from Alex Shepherd and Franz-Peter
//   With sincere thanks

// ******** UNLESS YOU WANT ALL CV'S RESET UPON EVERY POWER UP
// ******** AFTER THE INITIAL DECODER LOAD REMOVE THE "//" IN THE FOOLOWING LINE!!
#define DECODER_LOADED

// ******** REMOVE THE "//" IN THE FOOLOWING LINE TO SEND DEBUGGING
// ******** INFO TO THE SERIAL MONITOR
//#define DEBUG

#include <NmraDcc.h>
#include <SoftwareServo.h>

SoftwareServo servo[16];
#define servo_start_delay 50
#define servo_init_delay 7
#define servo_slowdown  3   //servo loop counter limit
int servo_slow_counter = 0; //servo loop counter to slowdown servo transit

int tim_delay = 500;
int numfpins = 17;
byte fpins [] = {3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19};
const int FunctionPin0 = 3;
const int FunctionPin1 = 4;
const int FunctionPin2 = 5;
const int FunctionPin3 = 6;
const int FunctionPin4 = 7;
const int FunctionPin5 = 8;
const int FunctionPin6 = 9;
const int FunctionPin7 = 10;
const int FunctionPin8 = 11;
const int FunctionPin9 = 12;
const int FunctionPin10 = 13;
const int FunctionPin11 = 14;     //A0
const int FunctionPin12 = 15;     //A1
const int FunctionPin13 = 16;     //A2
const int FunctionPin14 = 17;     //A3
const int FunctionPin15 = 18;     //A4
const int FunctionPin16 = 19;     //A5
NmraDcc  Dcc ;
DCC_MSG  Packet ;

int t;                                    // temp
#define SET_CV_Address       19           // THIS ADDRESS IS FOR SETTING CV'S Like a Loco
#define Accessory_Address    20           // THIS ADDRESS IS THE START OF THE SWITCHES RANGE
                                          // WHICH WILL EXTEND FOR 16 MORE SWITCH ADDRESSES
uint8_t CV_DECODER_MASTER_RESET =   120;  // THIS IS THE CV ADDRESS OF THE FULL RESET
#define CV_To_Store_SET_CV_Address    121
#define CV_Accessory_Address CV_ACCESSORY_DECODER_ADDRESS_LSB
                          
struct QUEUE
{
  int inuse;
  int current_position;
  int increment;
  int stop_value;
  int start_value;
};
QUEUE *ftn_queue = new QUEUE[16];

struct CVPair
{
  uint16_t  CV;
  uint8_t   Value;
};
CVPair FactoryDefaultCVs [] =
{
  {CV_ACCESSORY_DECODER_ADDRESS_LSB, Accessory_Address},
  {CV_ACCESSORY_DECODER_ADDRESS_MSB, 0},
  {CV_MULTIFUNCTION_EXTENDED_ADDRESS_MSB, 0},
  {CV_MULTIFUNCTION_EXTENDED_ADDRESS_LSB, 0},
  {CV_DECODER_MASTER_RESET, 0},
  {CV_To_Store_SET_CV_Address, SET_CV_Address},
  {CV_To_Store_SET_CV_Address+1, 0},
  {30, 2}, //F0 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {31, 1},    //F0 Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {32, 28},   //F0  Start Position F0=0
  {33, 140},  //F0  End Position   F0=1
  {34, 28},   //F0  Current Position
  {35, 2},  //F1 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {36, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {37, 28},   //  Start Position Fx=0
  {38, 140},  //  End Position   Fx=1
  {39, 28},  //  Current Position
  {40, 2},  //F2 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {41, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {42, 28},   //  Start Position Fx=0
  {43, 140},  //  End Position   Fx=1
  {44, 28},    //  Current Position
  {45, 2}, //F3 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {46, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {47, 28},   //  Start Position Fx=0
  {48, 140},  //  End Position   Fx=1
  {49, 28},    //  Current Position
  {50, 2}, //F4 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {51, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {52, 28},    //  Start Position Fx=0
  {53, 140},    //  End Position   Fx=1
  {54, 28},    //  Current Position
  {55, 2}, //F5 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {56, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {57, 28},    //  Start Position Fx=0
  {58, 140},    //  End Position   Fx=1
  {59, 28},    //  Current Position
  {60, 2}, //F6 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {61, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {62, 28},    //  Start Position Fx=0
  {63, 140},    //  End Position   Fx=1
  {64, 28},    //  Current Position
  {65, 2}, //F7 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {66, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {67, 28},   //  Start Position Fx=0
  {68, 140},  //  End Position   Fx=1
  {69, 28},    //  Current Position
  {70, 2}, //F8 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {71, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {72, 28},   //  Start Position Fx=0
  {73, 140},  //  End Position   Fx=1
  {74, 28},    //  Current Position
  {75, 2}, //F9 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {76, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {77, 28},   //  Start Position Fx=0
  {78, 140},  //  End Position   Fx=1
  {79, 28},    //  Current Position
  {80, 2}, //F10 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {81, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {82, 28},   //  Start Position Fx=0
  {83, 140},  //  End Position   Fx=1
  {84, 28},    //  Current Position
  {85, 2}, //F11 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {86, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {87, 28},   //  Start Position Fx=0
  {88, 140},  //  End Position   Fx=1
  {89, 28},    //  Current Position
  {90, 2}, //F12 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {91, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {92, 28},   //  Start Position Fx=0
  {93, 140},  //  End Position   Fx=1
  {94, 28},    //  Current Position
  {95, 2}, //F13 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {96, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {97, 28},   //  Start Position Fx=0
  {98, 140},  //  End Position   Fx=1
  {99, 28},    //  Current Position
  {100, 2}, //F14 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {101, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {102, 28},   //  Start Position Fx=0
  {103, 140},  //  End Position   Fx=1
  {104, 28},    //  Current Position
  {105, 2}, //F15 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {106, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {107, 28},   //  Start Position Fx=0
  {108, 140},  //  End Position   Fx=1
  {109, 28},    //  Current Position
  {110, 2}, //F16 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {111, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {112, 28},   //  Start Position Fx=0
  {113, 140},  //  End Position   Fx=1
  {114, 28},    //  Current Position
//FUTURE USE
  {115, 0}, //F17 Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
  {116, 1},    // Rate  Blink=Eate,PWM=Rate,Servo=Rate
  {117, 28},   //  Start Position Fx=0
  {118, 50},  //  End Position   Fx=1
  {119, 28},    //  Current Position
};
uint8_t FactoryDefaultCVIndex = sizeof(FactoryDefaultCVs)/sizeof(CVPair);
void notifyCVResetFactoryDefault()
{
  // Make FactoryDefaultCVIndex non-zero and equal to num CV's to be reset
  // to flag to the loop() function that a reset to Factory Defaults needs to be done
  FactoryDefaultCVIndex = sizeof(FactoryDefaultCVs)/sizeof(CVPair);
};

void setup()   //******************************************************
{
#ifdef DEBUG
  Serial.begin(115200);
#endif
  int i;
  uint8_t cv_value;
  // initialize the digital pins as outputs
    for (int i=0; i < numfpins; i++) {
      pinMode(fpins[i], OUTPUT);
      digitalWrite(fpins[i], 0);
     }
  for (int i=0; i < numfpins; i++) {
     digitalWrite(fpins[i], 1);
     delay (tim_delay/10);
  }
  delay( tim_delay);
  for (int i=0; i < numfpins; i++) {
     digitalWrite(fpins[i], 0);
     delay (tim_delay/10);
  }
  delay( tim_delay);
 
  // Setup which External Interrupt, the Pin it's associated with that we're using
  Dcc.pin(0, 2, 0);
  // Call the main DCC Init function to enable the DCC Receiver
  Dcc.init( MAN_ID_DIY, 100, FLAGS_OUTPUT_ADDRESS_MODE | FLAGS_DCC_ACCESSORY_DECODER, CV_To_Store_SET_CV_Address);
  delay(800);
   
  #if defined(DECODER_LOADED)
  if ( Dcc.getCV(CV_DECODER_MASTER_RESET)== CV_DECODER_MASTER_RESET )
  #endif  
 
     {
       for (int j=0; j < sizeof(FactoryDefaultCVs)/sizeof(CVPair); j++ )
         Dcc.setCV( FactoryDefaultCVs[j].CV, FactoryDefaultCVs[j].Value);
         digitalWrite(fpins[14], 1);
         delay (1000);
         digitalWrite(fpins[14], 0);
     }
  for ( i=0; i < numfpins; i++) {
    cv_value = Dcc.getCV( 30+(i*5)) ;   
#ifdef DEBUG
    Serial.print(" cv_value: ");
    Serial.println(cv_value, DEC) ;
#endif
    switch ( cv_value ) {
      case 0:   // LED on/off
        ftn_queue[i].inuse = 0;
        break;
      case 1:   // LED Blink
         {
           ftn_queue[i].inuse = 0;
           ftn_queue[i].current_position = 0;
           ftn_queue[i].start_value = 0;
           ftn_queue[i].increment = int (char (Dcc.getCV( 31+(i*5))));
           digitalWrite(fpins[i], 0);
           ftn_queue[i].stop_value = int(Dcc.getCV( 33+(i*5))) ;
         }
        break;
      case 2:   //servo
       {
         ftn_queue[i].current_position =int (Dcc.getCV( 34+(i*5)));
         ftn_queue[i].stop_value = int (Dcc.getCV( 33+(i*5)));
         ftn_queue[i].start_value = int (Dcc.getCV( 32+(i*5)));
         ftn_queue[i].increment = -int (char (Dcc.getCV( 31+(i*5))));
         // attaches servo on pin to the servo object
         servo[i].attach(fpins[i]);

#ifdef DEBUG
     Serial.print("InitServo ID= ");
     Serial.println(i, DEC) ;
#endif
     servo[i].write(ftn_queue[i].start_value);
         for (t=0; t<servo_start_delay; t++)
        {SoftwareServo::refresh();delay(servo_init_delay);}
        ftn_queue[i].inuse = 0;
        servo[i].detach();
        }
       break;
       case 3:   // DOUBLE ALTERNATING LED Blink
         {
           ftn_queue[i].inuse = 0;
       ftn_queue[i].current_position = 0;
           ftn_queue[i].start_value = 0;
           ftn_queue[i].increment = Dcc.getCV( 31+(i*5));
           digitalWrite(fpins[i], 0);
           digitalWrite(fpins[i+1], 0);
           ftn_queue[i].stop_value = int(Dcc.getCV( 33+(i*5)));
         }
         break;
       case 4:   // Simple Pulsed Output based on saved Rate =10*Rate in Milliseconds
         {
           ftn_queue[i].inuse = 0;
           ftn_queue[i].current_position = 0;
           ftn_queue[i].increment = 10 * int (char (Dcc.getCV( 31+(i*5))));
           digitalWrite(fpins[i], 0);
         }
         break;
       case 5:   // Fade On

         break;         
       case 6:   // NEXT FEATURE to pin
         break;         
       default:
         break;
    }
  }
}

void loop()   //**********************************************************************
{
  //MUST call the NmraDcc.process() method frequently
  // from the Arduino loop() function for correct library operation
  Dcc.process();
  SoftwareServo::refresh();
  delay(8);
  for (int i=0; i < numfpins; i++) {
    if (ftn_queue[i].inuse==1)  {

    switch (Dcc.getCV( 30+(i*5))) {
      case 0:
        break;
      case 1:
        ftn_queue[i].current_position = ftn_queue[i].current_position + ftn_queue[i].increment;
        if (ftn_queue[i].current_position > ftn_queue[i].stop_value) {
          ftn_queue[i].start_value = ~ftn_queue[i].start_value;
          digitalWrite(fpins[i], ftn_queue[i].start_value);
          ftn_queue[i].current_position = 0;
          ftn_queue[i].stop_value = int(Dcc.getCV( 33+(i*5)));
        }
        break;
      case 2:
        {
      if (servo_slow_counter++ > servo_slowdown)
        {
        ftn_queue[i].current_position = ftn_queue[i].current_position + ftn_queue[i].increment;
        if (ftn_queue[i].increment > 0) {
          if (ftn_queue[i].current_position > ftn_queue[i].stop_value) {
        ftn_queue[i].current_position = ftn_queue[i].stop_value;
                ftn_queue[i].inuse = 0;
        servo[i].detach();
              }
            }
        if (ftn_queue[i].increment < 0) {
          if (ftn_queue[i].current_position < ftn_queue[i].start_value) {
            ftn_queue[i].current_position = ftn_queue[i].start_value;
                ftn_queue[i].inuse = 0;
        servo[i].detach();
              }
        }
            servo[i].write(ftn_queue[i].current_position);
            servo_slow_counter = 0;
        }
       }
        break;
      case 3:
        ftn_queue[i].current_position = ftn_queue[i].current_position + ftn_queue[i].increment;
        if (ftn_queue[i].current_position > ftn_queue[i].stop_value) {
          ftn_queue[i].start_value = ~ftn_queue[i].start_value;
          digitalWrite(fpins[i], ftn_queue[i].start_value);
          digitalWrite(fpins[i]+1, ~ftn_queue[i].start_value);
          ftn_queue[i].current_position = 0;
          ftn_queue[i].stop_value = int(Dcc.getCV( 33+(i*5)));
        }
        i++;
        break;
       case 4:   // Simple Pulsed Output based on saved Rate =10*Rate in Milliseconds
         {
           ftn_queue[i].inuse = 0;
           ftn_queue[i].current_position = 0;
           ftn_queue[i].increment = 10 * int (char (Dcc.getCV( 31+(i*5))));
           digitalWrite(fpins[i], 0);
         }
         break;
       case 5:   // Fade On
         {
           ftn_queue[i].inuse = 0;
           ftn_queue[i].start_value = 0;
           ftn_queue[i].increment = int (char (Dcc.getCV( 31+(i*5))));
           digitalWrite(fpins[i], 0);
           ftn_queue[i].stop_value = int(Dcc.getCV( 33+(i*5))) *10.;
         }
         break;         
       case 6:   // NEXT FEATURE to pin
         break;         
       default:
         break;   
      }
    }
  }
}

extern void notifyDccAccState( uint16_t Addr, uint16_t BoardAddr, uint8_t OutputAddr, uint8_t State) {
  uint16_t Current_Decoder_Addr;
  uint8_t Bit_State;
  Current_Decoder_Addr = Dcc.getAddr();
  Bit_State = OutputAddr & 0x01;
 
  if ( Addr >= Current_Decoder_Addr && Addr < Current_Decoder_Addr+17) { //Controls Accessory_Address+16
#ifdef DEBUG
     Serial.print("Addr = ");
     Serial.println(Addr);
     Serial.print("BoardAddr = ");
     Serial.println(BoardAddr);
     Serial.print("Bit_State = ");
     Serial.println(Bit_State);
#endif
    exec_function(Addr-Current_Decoder_Addr, Bit_State );
  }
}
void exec_function (int function, int FuncState)  {
  byte pin;
  int servo_temp;
  pin = fpins[function];
  switch ( Dcc.getCV( 30+(function*5)) )  {  // Config 0=On/Off,1=Blink,2=Servo,3=DBL LED Blink,4=Pulsed,5=fade
    case 0:    // On - Off LED
      digitalWrite (pin, FuncState);
      ftn_queue[function].inuse = 0;
      break;
    case 1:    // Blinking LED
      if ((ftn_queue[function].inuse==0) && (FuncState==1))  {
        ftn_queue[function].inuse = 1;
        ftn_queue[function].start_value = 0;
        digitalWrite(pin, 0);
        ftn_queue[function].stop_value = int(Dcc.getCV( 33+(function*5)));
      } else {
          if ((ftn_queue[function].inuse==1) && (FuncState==0)) {
            ftn_queue[function].inuse = 0;
            digitalWrite(pin, 0);
          }
        }
      break;
    case 2:    // Servo
      if (ftn_queue[function].inuse == 0)  {
        ftn_queue[function].inuse = 1;
        servo[function].attach(pin);
      }
      if (FuncState==1) ftn_queue[function].increment = char ( Dcc.getCV( 31+(function*5)));
        else ftn_queue[function].increment = - char(Dcc.getCV( 31+(function*5)));
      if (FuncState==1) ftn_queue[function].stop_value = Dcc.getCV( 33+(function*5));
        else ftn_queue[function].stop_value = Dcc.getCV( 32+(function*5));
      break;
    case 3:    // Blinking LED PAIR
      if ((ftn_queue[function].inuse==0) && (FuncState==1))  {
        ftn_queue[function].inuse = 1;
        ftn_queue[function].start_value = 0;
        digitalWrite(fpins[function], 0);
        digitalWrite(fpins[function+1], 1);
        ftn_queue[function].stop_value = int(Dcc.getCV( 33+(function*5)));
      } else {
          if (FuncState==0) {
            ftn_queue[function].inuse = 0;
            digitalWrite(fpins[function], 0);
            digitalWrite(fpins[function+1], 0);
          }
        }
      break;
    case 4:    // Pulse Output based on Rate*10 Milliseconds
      if ((ftn_queue[function].inuse==0) && (FuncState==1)) {  //First Turn On Detected
        digitalWrite(fpins[function], 1);
        delay (10*ftn_queue[function].increment);
        digitalWrite(fpins[function], 0);
        ftn_queue[function].inuse = 1;                    //inuse set to 1 says we already pulsed
      } else
          if (FuncState==0)  ftn_queue[function].inuse = 0;
      break;      
    case 5:    // Fade On
#define fadedelay 24
      if ((ftn_queue[function].inuse==0) && (FuncState==1))  {
        ftn_queue[function].inuse = 1;
        for (t=0; t<ftn_queue[function].stop_value; t+=ftn_queue[function].increment) {
          digitalWrite( fpins[function], 1);
          delay(fadedelay*(t/(1.*ftn_queue[function].stop_value)));
          digitalWrite( fpins[function], 0);
          delay(fadedelay-(fadedelay*(t/(1.*ftn_queue[function].stop_value))));
        }
        digitalWrite( fpins[function],  1 );
      } else {
          if ((ftn_queue[function].inuse==1) && (FuncState==0)) {
            ftn_queue[function].inuse = 0;
            digitalWrite(fpins[function], 0);
          }
        }
      break;
    case 6:    // Future Function
      ftn_queue[function].inuse = 0;
      break;
    default:
      ftn_queue[function].inuse = 0;
      break;
    }
}

-----------------------

Another thing that I noticed, is that when i use the untouched version and change the address from Powercab by altering CV1 from value 40 to 20, it works.

But as soon as I enter the CVs relative to the functions F15(105-109) and F16(110-114), the decoder becomes unresponsive, and I have to reset it by entering CV120=120.

At first I thought it was my mistake because I was altering only F16, leaving F15 untouched. Since F15 is programmed as blinking led, it may interfere with F16, but also if I change the CVs to servo for F15 and F16 the decoder goes numb.

Is there any remote possibility to be a bug in the sketch? I also have some suspicions about channels A4 and A5 of Arduino being special ones (they are not aligned with the other-is it merely a layout issue?-), and not possible to connect a servo to them, at this point. Does it make sense what I'm saying?

I'm completely lost.... eheheheh

Best Regards

 

Roberto

geoffb's picture

@Roberto re: Servos

Hi Roberto,

There is nothng wrong with your modifications. When you enables the 17th servo (CV 110) you touched a new bug introduced by me in mods to V5.2. There is a simple fix! Change

#include <NmraDcc.h>
#include <SoftwareServo.h>

SoftwareServo servo[16];
#define servo_start_delay 50

TO:

#include <NmraDcc.h>
#include <SoftwareServo.h>

SoftwareServo servo[17];
#define servo_start_delay 50

and your decoder will work. Or... only use 16 or fewer servos.

I will change the library when I have time and upload the new version.

Have fun!  smiley

Best regards,

Geoff Bunza

Servos

Hi Geoff,

thanks for answering. I noticed that something wrong happened when i was changing the CV range 110-114 to work as a servo, that's why I asked you if there was a possibility for it to be a bug. Glad it's easy to fix.

Keep up the good work.

Grateful,

 

Roberto

Finally dove in and have one built!

Hi Geoff

I must first say thank you for all of your work, kindness, and insight throughout the blog detailing DCC, the Arduino, and so many servos controlled!  I thought of using an Ardino quite a while ago and found your blog when I was going to dive into the DCC side of things.  Although I think finding it also made me a bit lazy seeing you already did it, all I had to do was build it!  (Four kids does takes its toll on free time :-) )

Well I finally completed the first system (only 12 months after buying the parts - again selective use of that valuable "time" thing) and all works perfectly.  I had been thinking of using pushbuttons for facia panel control as previously discussed in this blog and simply ask if you had ever implemented push button and LED control direct with the many options you have done thus far.  Since getting it operable, I thought it would be cool to use a paired pin as input/output straight off the headers so one hardware build could also function as turnout control or lighting/animation. One pushbutton, two LEDs and a relay on a basic daughter board connected simply with a 3-wire extension.

If you have not, I suppose I have defined my own marching orders for the next quantity of free hours I find. Finally forcing myself to dig deeper into Arduino programming is also as good a reason as any!

Thank you again!

Brian

geoffb's picture

@Brian re:Pushbuttons

Hi Brian,

I'm really glad you are enjoying this!

There are at least 3 ways to deal with "buttons" for additional control: You could use an additional pin per button and reduce the number of control functions in a decoder -- I never seem to favor this but it should be straightforward to look for the pushbutton settings and set the internal state of the function, and let the decoder handle the rest-- of course this invites timing problems; you could build a DCC controller built specifically for DCC control, drive your set of decoders with it on its own DCC bus, and interface the buttons to the controller ( look here:SMA25 Tinkering with DCC++ Base Stations: JMRI To Track Connections, Accessories & Fun  http://model-railroad-hobbyist.com/node/27318    or here: SMA22 – Low Cost DCC Controller/Function Generator for Animation, Test Tracks, Absolute Stopping Blocks for Keep-Alive DCC Locos   http://model-railroad-hobbyist.com/node/25045  for some DIY controller ideas), or you could drop the DCC control aspect and use fascia panel buttons for control (even from multiple points) like the arduino projects found here: A modeler's introduction to the Arduino  (Pages 35-40) http://mrhpub.com/2016-12-dec/online/

Please note: the most recent entry with data for these decoders can be found here:  SMA20 New Low Cost 17 Channel DCC Decoders & Dual Motor,LED, & Servo Control Updated: 6 Ftns/Pin & New Features  http://model-railroad-hobbyist.com/node/24316  and there is an interesting update coming soon.
 
Some food for thought... Have fun!  smiley
Best regards,
Geoff Bunza
Scott Forbes's picture

Individual Addressing

Geoff-

The decoder I cobbled together from several other tutorials also works well with your sketch. Is it possible to create a sketch that would allow each output pin to have a dcc address rather than having a single address with Function controls under it? If so, would it be possible to have the addresses be user configurable? If I could assign each pin it's own address and have functions under that address to set the travel and speed of the servos, it would be infinitely more usable to me. I do all of my control through JMRI and am not sure how (short of writing lots of nasty Jython scripts) I could get the function decoder to play nice with Panel Pro. What I want is similar to the Tam Valley servo controllers. I suppose I could figure it out given long enough, but if someone else has done it I don't want to reinvent the wheel.

I have a sketch now that does something similar, but each pin has to have the address hard coded into the sketch. The idea of being able to assign addresses to the pins and set travel via CVs is very appealing.

Thanks in advance. All of this electronics stuff is a blast - even if somewhat trying and a steep learning curve.

Regards,

 Scott Forbes
 General Superintendant
 Pacific Coast Railroad Co.
 
geoffb's picture

@Forbescraft re: Multi address functions

Hi Forbescraft  (name?),

Is it possible to create a sketch that would allow each output pin to have a dcc address rather than having a single address with Function controls under it?
Perhaps, but not easily with the NmraDcc library, which sets one address to respond to. It doesn't really have a mechanism to look for multiple mobile/function decoder addresses simultaneously. One might consider modifying the library to look for a range of addresses, but then it would need to keep track of commands received with the associated address explicitly, which would be more work, and likely entail modifications internally. There may also be a hidden problem with the Pro Mini processing what would likely be many more DCC commands. Remember that the DCC commands are repeated by the base station.
 
These decoders should already "play nice with Panel Pro." What functionality are you looking for, relative to the Tam Valley controller capabilities?
 
The current entry for these decoders can be found here: SMA20 New Low Cost 17 Channel DCC Decoders & Dual Motor,LED, & Servo Control Updated: 6 Ftns/Pin & New http://model-railroad-hobbyist.com/node/24316
Please place future comments there.
 
Have fun!  smiley
Best regards,
Geoff Bunza
 

Controlling Frog Polarity

Geoff, someone asked about controlling frog polarity. Rodney Edington has developed a servo mount (3D printed) that mimics a tortoise once the servo is installed. It has an optional mounting point for an spdt or dpdt micro switch. This allows easy and inexpensive control of frog polarity with only a few inches of wire between the micro switch and the frog. We opted for this less elegant solution just because it was so simple. If folks are making their own mounts regardless of their method or material, adding the micro switch would be really simple.

Lynn Masoner

geoffb's picture

@Lynn re: Rodney Edington servo mounts

Hi Lynn,

There was enough interest in powering frogs that if you have a URL to Rodney Edington's servo mounts it would likely be apprecaied by many modelers. I tried unsuccesssfully googling "Rodney Edington servo mount" to no avail.

Have fun!  smiley
Best regards,
Geoff Bunza

>> Posts index


Journals/Blogs

Recent Blog posts: