Jump to content
 

Arduino accessory decoder experiments


Recommended Posts

Hi to all :)

I would share my version of DCC Servo decoder starting form the Dave code, the sketch includes some new functions.

 

The EEprom management: all the servoo settings are stored as the angle position and the last position and read at reset; typing 'r' on the monitor console the initial values are restored at the next restart ofthe board.

 

Another function is the decoder address set by the command station: pushing the button after a reset, the led is on and sending an accessory command form DCC command station the decoder stores the address as "base address" of the decoder, the servo addresses will be the base value plus 1,2,3,4. After a servo command, the button has the initial function for servo position setting. This function is availableonly in DCC-mode.

Futhermore, during the servo position setting, the led flashes with differet times based on the "status": pushing the first time the led flashes slowly, for the next two phases (close and thrown setting) the led flashes quicker, this will help when serial monitor isnot available...

 

Each servo should be connected by a pull-up resistor to the control pin, I used 10k but 4k7 should be better. This should avoid the initial "glitch" at power-up. Anyway, to avoid the servo "buzzing" (for cheap motors) the code "detach" the servo after a movement, this means that the control pin is disconnected.

Of course the code is still on debug phase, I didn't test on a real layout but just on the table! :)

I used an Arduino UNO R3 board, with two cheap servo (Tower PRO SG-90) and optocoupler circuit to interface the DCC command station (see mynabay.com for circuit as principle, I used CNX83 instead 6N137). The push buttonis connected to A0 pin (analog port) and this require an external pull-up resistor, 10k are ok. I used also an external power supply instead USB power from PC.

ciao!
antogar

 

c25B31.jpg

////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// DCC_Accessory_Decoder
//
// Author: Dave Renshaw
// Modified: Antonino Gargiulo 8th dec 2014
//
// This sketch provides basic accessory decoder capabilites for operating servos or Tortoise pont machines. It uses
// the DCC_Decoder library created by Mynabay (http://www.mynabay.com/arduino/2-uncategorised/14-arduino-dcc-monitor)
//
// BEWARE This version is far from complete!
//

// http://www.rmweb.co.uk/community/index.php?/topic/70389-arduino-accessory-decoder-experiments/

#include <DCC_Decoder.h>
#include <Servo.h> 
#include <EEPROM.h>

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// The configuration of the decoder is done via a string that is either retrieved from the persistent storage or
// sent via the serial interface. The format is as follows...
//
//   - DCC address terminated by a '#' character
// For a servo device...
//   - Device type character 's'
//   - PWM output port to use
//   - Switch input port to use
//   - Closed angular displacement in degrees terminated by a '#' character
//   - Thrown angular displacement in degrees terminated by a '#' character
// For a motor driven point machine
//   - Device type character 'm' 
//   - DCC accessory address 
//   - Digital output port to which 5volts is delivered to close terminated by a '#' character
//   - Digital output port to which 5volts is delivered to throw terminated by a '#' character
//   - Switch input port to use
//
// Arduino Uno, Nano and ProMini config data:
//    PWM output ports:  3,5,6,9,10,11
//    Interrupt ports:   2,3
//
char configData[] = "61#s03#04#060#120#62#s05#07#060#120#63#s06#08#060#120#64#s09#12#060#120#65#s10#11#060#120#!";

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Defines and structures
//
#define MAX_DEVICES               5
#define SET_UP                    A0  //pin button 

#define kDCC_INTERRUPT            0  // interrupt 0, pin 2 selected for DCC input

int DEBOUNCE_PERIOD =           500;           // the debounce time; increase if things get twitchy!

int TYPE_SERVO =                  1;
int TYPE_TORTOISE =               2;

boolean  dccMode = false;

unsigned long lastPacketMillis = 0;

int lastServoReferenced = -1;

boolean configOK = false;

int eepromaddr = 0;  
int timeServoMove = 250;
int ServoDetachTime = 1200;

int currentAddr;
boolean addressStored =0;

int ledLamp = 0;
boolean ledState = 0;
int long ledTime;

typedef struct
{
    int               address;                // Address to respond to
    int               type;                   // Type of accessory (servo, Tortoise, etc.)
    int               outputPin;              // Arduino output pin to drive
    int               switchPin;              // Arduino output pin to read for switch input
    boolean           thrown;                 // true=thrown, false=closed.
    int               closedSetting;          // Angular displacement for closed position
    int               thrownSetting;          // Angular displacement for thrown position
    Servo             servo;
    int               oldSwitchState;
    long              lastSwitchChangeTime;
} DCCAccessoryAddress;


DCCAccessoryAddress devices[MAX_DEVICES];


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Decoder Init 
//
void configureDecoder() {

  // Assume the config is bad...
  configOK = false;
  
  // parse the config string...
  int nextDevice = 0;
  int nextChar = 0;

  while (nextChar < sizeof(configData))
  {
    // If the next character is the terminator we are all done and things are OK (maybe)...
    if (configData[nextChar] == '!') {
      configOK = true;
      break;
    }
    
    int addr = 0;
    char c = configData[nextChar++] ;
    
    while (c != '#')
      {
        if (c < '0' || c > '9')
          {
            Serial.print("**Error in config string - DCC address invalid at character ");
            Serial.println(--nextChar);
            nextChar = 9999;
            break;
          }
          else
          {          
            addr *= 10;
            addr += c - '0';
            c = configData[nextChar++] ;
           }
      }    
      Serial.print("Received DCC address ");
      Serial.println(addr);

    char type;
    if (nextChar < sizeof(configData)) {
      type = configData[nextChar++];
    } else {
      Serial.print("**Error in config string - Expected device type at character ");
      Serial.println(nextChar);
    }

    Serial.print("Received type character '");
    Serial.print(type);
    Serial.println("'");
    
    if (type == 's')
    {
      // servo... pick out the output port number...
      int port = 0;
      c = configData[nextChar++] ;
      while (c != '#')
      {
        if (c < '0' || c > '9')
        {
          Serial.print("**Error in config string - output port invalid character '");
          Serial.print(c);
          Serial.println("'");
          nextChar = 9999;
          break;
        }
        else
        {
          port *= 10;
          port += c - '0';
          c = configData[nextChar++] ;
        }
      }
      Serial.print("Received port ");
      Serial.println(port);
      
      // ..then the switch port number...
      int sport = 0;
      c = configData[nextChar++] ;
      while (c != '#')
      {
        if (c < '0' || c > '9')
        {
          Serial.print("**Error in config string - switch port invalid character '");
          Serial.print(c);
          Serial.println("'");
          nextChar = 9999;
          break;
        }
        else
        {
          sport *= 10;
          sport += c - '0';
          c = configData[nextChar++] ;
        }
      }
      Serial.print("Received switch port ");
      Serial.println(sport);
      
      // ...then the closed and thrown angles...
      int closed = 0;     // angular position for closed
      int thrown = 0;     // angular position for thrown

      c = configData[nextChar++] ;
      while (c != '#')
      {
        if (c < '0' || c > '9')
        {
          Serial.print("**Error in config string - closed offset angle invalid character '");
          Serial.print(c);
          Serial.println("'");
          nextChar = 9999;
          break;
        }
        else
        {
          closed *= 10;
          closed += c - '0';
          c = configData[nextChar++] ;
        }
      }
      if (closed > 180)
      {
        Serial.println("**Error in config string - closed offset too big!");
        nextChar = 9999;
        break;
      }
      else
      {
        Serial.print("Received closed offset ");
        Serial.println(closed);
      }
      
      c = configData[nextChar++] ;
      while (c != '#')
      {
        if (c < '0' || c > '9')
        {
          Serial.print("**Error in config string - thrown offset angle invalid character '");
          Serial.print(c);
          Serial.println("'");
          nextChar = 9999;
          break;
        }
        else
        {
          thrown *= 10;
          thrown += c - '0';
          c = configData[nextChar++] ;
        }
      }
      if (thrown > 180)
      {
        Serial.println("**Error in config string - thrown offset too big!");
        nextChar = 9999;
        break;
      }
      else
      {
        Serial.print("Received thrown offset ");
        Serial.println(thrown);
      }

      // Device configuration complete fora Servo Motor... store in eeprom!
      
      devices[nextDevice].address = addr;
      devices[nextDevice].type = TYPE_SERVO;
      devices[nextDevice].outputPin = port;
      devices[nextDevice].switchPin = sport;
      devices[nextDevice].thrown = false;
      devices[nextDevice].closedSetting = closed;
      devices[nextDevice].thrownSetting = thrown;
      devices[nextDevice].oldSwitchState = 1;
      devices[nextDevice].lastSwitchChangeTime = millis();
      
      eepromaddr = nextDevice * 7;
      
      EEPROM.write(eepromaddr,addr);
      EEPROM.write(eepromaddr+1,type);
      EEPROM.write(eepromaddr+2,port);
      EEPROM.write(eepromaddr+3,sport);
      EEPROM.write(eepromaddr+4,closed);
      EEPROM.write(eepromaddr+5,thrown);
      EEPROM.write(eepromaddr+6,0);      //state of the switch, innitialized as closed

      Serial.println ("******************************");
      Serial.print ("Stored data for Device ");
      Serial.println (nextDevice);
      Serial.print (addr);
      Serial.print ("\t");
      Serial.print (type);
      Serial.print ("\t");
      Serial.print (port);
      Serial.print ("\t");
      Serial.print (sport);
      Serial.print ("\t");
      Serial.print (closed);
      Serial.print ("\t");
      Serial.println (thrown);
      Serial.println ("******************************");  
      
      nextDevice++;
      
    }
    else if (type == 'm')
    {
      // motor driven point machine...
    }
    else
    {
      // duff type...
      Serial.print("**Error in config string - Unrecognised device type '");
      Serial.print(type);
      Serial.print(" at character ");
      Serial.println(nextChar);
      nextChar = 9999;
      break;
    }
  }  // while nextChar
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Basic accessory packet handler 
//
void BasicAccDecoderPacket_Handler(int address, boolean activate, byte data)
{
    // Convert NMRA packet address format to human address
    address -= 1;
    address *= 4;
    address += 1;
    address += (data & 0x06) >> 1;
    
    Serial.print("Acc Packet ");
    Serial.println(address, DEC); 
   
    currentAddr = address; 

    boolean closed = (data & 0x01) ? 1 : 0;
       
    for(int i=0; i<(int)(sizeof(devices)/sizeof(devices[0])); i++)
    {
        if( address == devices[i].address )
        {
            if (devices[i].type == TYPE_SERVO)
            {
              lastServoReferenced = i;
              devices[i].thrown = !closed;
              devices[i].servo.attach(devices[i].outputPin);
              devices[i].lastSwitchChangeTime = millis();
              EEPROM.write(i*7+6,devices[i].thrown);  //store the switch status in EEprom 
              // Serial.print ("Servo ");
              // Serial.print (i);
              // Serial.print (" ");
              // Serial.print (devices[i].lastSwitchChangeTime);
              // Serial.print (" ");
              // Serial.println (closed);
               if (closed)
              {
                devices[i].servo.write(devices[i].closedSetting);
              } else {
                devices[i].servo.write(devices[i].thrownSetting);
              }
            } else if (devices[i].type == TYPE_TORTOISE )
            {
              if (closed)
              {
                digitalWrite(devices[i].outputPin + 1, LOW);
                digitalWrite(devices[i].outputPin, HIGH);
              } else {
                digitalWrite(devices[i].outputPin, LOW);
                digitalWrite(devices[i].outputPin + 1, HIGH);
              }
            }
        }
    }
    
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Basic accessory packet handler 
//
boolean RawPacket_Handler(byte count, byte* packet)
{
   lastPacketMillis = millis();
    if (!dccMode) {
      dccMode = true;
      Serial.println("DCC mode");
    }
    return false;   
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Setup
//
void setup() {
  
  Serial.begin(9600);
  DCC.SetBasicAccessoryDecoderPacketHandler(BasicAccDecoderPacket_Handler, true);
  DCC.SetRawPacketHandler(RawPacket_Handler);
  DCC.SetupDecoder( 0x00, 0x00, kDCC_INTERRUPT );
   
  // Enable the set-up/adjustment pushbutton...
  pinMode(SET_UP, INPUT);
  //digitalWrite(SET_UP, HIGH);

  // Set-up pin 13 so we can illuminate the LED for info...
  pinMode(13, OUTPUT);
  digitalWrite(13, ledState);
   
  // Do we have a saved configuration?
  
  if (EEPROM.read(0) != 255)
  {
    // Yes so read it back in...
    
    for(int i=0; i<(int)(sizeof(devices)/sizeof(devices[0])); i++)
    {
      eepromaddr = i * 7; //current servo base-address in eeprom
      
      devices[i].address = EEPROM.read(eepromaddr);
      if (EEPROM.read(eepromaddr+1)=='s')
          devices[i].type = TYPE_SERVO;
        else
          devices[i].type = TYPE_TORTOISE;
      devices[i].outputPin = EEPROM.read(eepromaddr+2);
      devices[i].switchPin = EEPROM.read(eepromaddr+3);
      devices[i].thrown = EEPROM.read(eepromaddr+6);
      devices[i].closedSetting = EEPROM.read(eepromaddr+4);
      devices[i].thrownSetting = EEPROM.read(eepromaddr+5);
      devices[i].oldSwitchState = 1;
      devices[i].lastSwitchChangeTime = millis();
      
      Serial.println ("******************************");
      Serial.print ("Read data for Device ");
      Serial.println (i);
      Serial.print (devices[i].address);
      Serial.print ("\t");
      Serial.print (devices[i].type);
      Serial.print ("\t");
      Serial.print (devices[i].outputPin);
      Serial.print ("\t");
      Serial.print (devices[i].switchPin);
      Serial.print ("\t");
      Serial.print (devices[i].closedSetting);
      Serial.print ("\t");
      Serial.print (devices[i].thrownSetting);      
      Serial.print ("\t");
      Serial.println (devices[i].thrown);      
      }
    Serial.println ("EEprom read!");
    configOK = true;    
    }
  else
  {
    // Read the configuration string and set up the relavent input and putput ports...
    configureDecoder();
  }

  // Do we have a valid config?
  if (configOK) {  
    Serial.println("Config OK");
    // Setup output pins
    for(int i=0; i<(int)(sizeof(devices)/sizeof(devices[0])); i++)
    {
      // if the device address is defined then we can try and set things up...
      if (devices[i].address != 0)
      {
        if ( devices[i].type == TYPE_SERVO )
        {
          pinMode( devices[i].outputPin, OUTPUT );
          devices[i].servo.attach(devices[i].outputPin);
          if (!devices[i].thrown) {
            devices[i].servo.write(devices[i].closedSetting);
           } else {
            devices[i].servo.write(devices[i].thrownSetting);
           } 
          delay (timeServoMove);
          devices[i].servo.detach();
          pinMode(devices[i].switchPin, INPUT);
          digitalWrite(devices[i].switchPin, HIGH);
          Serial.print("Servo addr: ");
          Serial.print(devices[i].address,DEC);
          Serial.print(" ready on pin: ");
          Serial.print(devices[i].outputPin,DEC);
          Serial.print(" and switchpin: ");
          Serial.println(devices[i].switchPin,DEC);    
        } 
        else
          if (devices[i].type == TYPE_TORTOISE )
            {
              pinMode( devices[i].outputPin, OUTPUT );
              pinMode( devices[i].outputPin + 1, OUTPUT );
              digitalWrite(devices[i].outputPin, HIGH);
              digitalWrite(devices[i].outputPin + 1 , LOW);
             }
      }
    }
  }
}


int oldSetUpButtonState = 1;
long lastSetUpButtonChangeTime = millis();

int setUpMode = 0;
long lastSetUpStep;
int setUpStep;
int STEP_INTERVAL = 300;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Main loop
//
void loop() {
  ////////////////////////////////////////////////////////////////
  // Loop DCC library
  DCC.loop();
    
  if (dccMode && (millis() - lastPacketMillis > 1000)) {
    dccMode = false;
    Serial.println("DC mode");
  } 
  
  for(int i=0; i<(int)(sizeof(devices)/sizeof(devices[0])); i++)
    {
      if (( setUpMode == 0) && devices[i].servo.attached() && (millis() - devices[i].lastSwitchChangeTime > ServoDetachTime)) {
        devices[i].servo.detach();
        Serial.print("Servo ");
        Serial.print(i);
        Serial.println(" detached");
      } 
    }

  // See if any command data is coming in...
  while (Serial.available()) {
    char cmd = Serial.read();  // Are we waiting for a command?
    if (cmd == 'r')            // Reset of the configuration, write 255 at first position of EEprom
     {
       EEPROM.write(0,255);
       Serial.println ("Reset Configuration!");
     }
  }
  
  // look at the set_up push button...
  int newState = digitalRead(SET_UP);
  
  if (newState != oldSetUpButtonState) {
    long timeNow = millis();
    if ((timeNow - lastSetUpButtonChangeTime) > DEBOUNCE_PERIOD){
      lastSetUpButtonChangeTime = timeNow;
      // whatever the reading is at, it's been there for longer
      // than the debounce delay, so take it as the actual current state:

      // Push Button pressed after reset
      
      if (newState == LOW && lastServoReferenced < 0 && dccMode) {
        if (addressStored) {
              for(int i=0; i<(int)(sizeof(devices)/sizeof(devices[0])); i++)
                {
                  devices[i].address = currentAddr+i;
                  EEPROM.write(i*7,devices[i].address);
                }
              Serial.print("currentAcc Packet ");
              Serial.println(currentAddr, DEC);
              digitalWrite (13, LOW);
              addressStored = 0;
           } else {
              Serial.println ("...aspetto indirizzo");
              digitalWrite (13, HIGH);
              addressStored = 1;
             }
        }
       
      // Push Button pressed after servo movement
      
      if (newState == LOW && lastServoReferenced >= 0) {
        setUpMode++;
        Serial.print("set_up mode=");
        Serial.println(setUpMode);
        Serial.print("Setting Servo = ");
        Serial.println(lastServoReferenced);
        devices[lastServoReferenced].servo.attach(devices[lastServoReferenced].outputPin);
        if (setUpMode == 1)
        {
          lastSetUpStep = timeNow;
          // starting set up so move servo to middle position...
          setUpStep = 90;
          devices[lastServoReferenced].servo.write(setUpStep);
        }
        else if (setUpMode == 3)
        {
          devices[lastServoReferenced].closedSetting = setUpStep;
          Serial.print ("New value for close setting for device ");
          Serial.println (lastServoReferenced);
          Serial.println (setUpStep);
          EEPROM.write(((lastServoReferenced*7)+4),setUpStep);          
          setUpStep = 90;
          devices[lastServoReferenced].servo.write(setUpStep);
        }
        else if (setUpMode == 4)
        {
          devices[lastServoReferenced].thrownSetting = setUpStep;
          Serial.print ("New value for thrown setting for device ");
          Serial.println (lastServoReferenced);
          Serial.println (setUpStep);
          EEPROM.write(((lastServoReferenced*7)+5),setUpStep);          
          devices[lastServoReferenced].servo.write(setUpStep);
          
          setUpMode = 0;
          Serial.println ("State 0");
          devices[lastServoReferenced].servo.detach();
        }
      }
      oldSetUpButtonState = newState;
    }
  }
  
  // do the servo set up thing...
  if (setUpMode > 0)
  {
    if ((millis() - lastSetUpStep) > STEP_INTERVAL)
      {
        if (setUpMode == 2)
          {
            setUpStep--;
            devices[lastServoReferenced].servo.write(setUpStep);
            lastSetUpStep = millis();
          }
          else if (setUpMode == 3)
          {
            setUpStep++;
            devices[lastServoReferenced].servo.write(setUpStep);
            lastSetUpStep = millis();
          }
      }
    }

  
  // look at the switches if we are in DC mode...
  if (!dccMode && ( setUpMode == 0)) {
    
    for(int i=0; i<(int)(sizeof(devices)/sizeof(devices[0])); i++) {
      int newStateSwitch = digitalRead(devices[i].switchPin);
  
      if (newStateSwitch != devices[i].oldSwitchState) {
        long timeNow = millis();
        if ((timeNow - devices[i].lastSwitchChangeTime) > DEBOUNCE_PERIOD) {
          devices[i].lastSwitchChangeTime = timeNow;
          // whatever the reading is at, it's been there for longer
          // than the debounce delay, so take it as the actual current state:
          devices[i].servo.attach(devices[i].outputPin);
          Serial.print("devices[");
          Serial.print(i);
          Serial.print("] new switch state=");
          if (newStateSwitch == LOW) {
            Serial.println("LOW");
            devices[i].servo.write(devices[i].closedSetting);
          } else {
            Serial.println("HIGH");
            devices[i].servo.write(devices[i].thrownSetting);
          }
          devices[i].oldSwitchState = newStateSwitch;
          lastServoReferenced = i;
        }
      }
    }
  }
  
  // Led on pin 13 management
  
  if (setUpMode > 0) {
    switch(setUpMode){
      case 1:
        ledLamp=700;
      break;
      case 2:
        ledLamp=300;
      break;
      case 3:
        ledLamp=100;
      break;
      default:
        ledLamp=0;
      }
    if (millis()-ledTime > ledLamp) {
      ledTime = millis();
      ledState = !ledState;
      digitalWrite(13, ledState);   
      }
    }
    else if (lastServoReferenced >= 0) {
       digitalWrite(13, LOW);   
      }
  
  
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



Edited by antogar
  • Like 5
Link to post
Share on other sites

  • RMweb Premium

Good stuff Antonino. I like the idea of detaching the servos after moving them to avoid jitter/chatter. On a couple of my prototypes I used a relay driven by an NPN transistor circuit controlled by the Arduino to turn on and off the power supply to the servos. With this I turned the supply off about 2 seconds after changing the position of a servo. This certainly stops chatter and also avoids the servos flicking about during start up. Your solution is more elegant so I will definitely give it a try.

 

I'm not sure I would update the EEPROM on every position change. The EEPROM has a theoretical erase/write cycle limit of 100,000 operations (though probably much more in practice). Whether use on the average layout would ever get you over that limit is a good question.

 

Cheers

Dave

Edited by eldavo
Link to post
Share on other sites

 

Each servo should be connected by a pull-up resistor to the control pin, I used 10k but 4k7 should be better. This should avoid the initial "glitch" at power-up. Anyway, to avoid the servo "buzzing" (for cheap motors) the code "detach" the servo after a movement, this means that the control pin is disconnected.

Are you using the resistor between the signal wire and 5v?

I have used a pull-down resistor (from signal to GND) for the same purpose.

 

In your picture there seems to be a wire from the Arduino 5v pin. I hope that is not being used to power the servos - that will lead to frustrating problems if not actual damage to your Arduino. Give the servos a separate power supply with servo GND connected to Arduino GND.

 

...R

Link to post
Share on other sites

Good stuff Antonino. I like the idea of detaching the servos after moving them to avoid jitter/chatter. On a couple of my prototypes I used a relay driven by an NPN transistor circuit controlled by the Arduino to turn on and off the power supply to the servos. With this I turned the supply off about 2 seconds after changing the position of a servo. This certainly stops chatter and also avoids the servos flicking about during start up. Your solution is more elegant so I will definitely give it a try.

 

I'm not sure I would update the EEPROM on every position change. The EEPROM has a theoretical erase/write cycle limit of 100,000 operations (though probably much more in practice). Whether use on the average layout would ever get you over that limit is a good question.

 

Cheers

Dave

 

 

Thanks Dave!

 

You are right about the EEprom, I was thinkng to my layout (260x130cm... small!) with a few number of turnout and really small number of switch movements because it is a "walkway" layout.

For the detach function, I got the suggestion from the web and an italian modeler who developed an accessory decoder for servo based on PIC platform. Anyway, it seems to work properly.

 

 

Are you using the resistor between the signal wire and 5v?

I have used a pull-down resistor (from signal to GND) for the same purpose.

 

In your picture there seems to be a wire from the Arduino 5v pin. I hope that is not being used to power the servos - that will lead to frustrating problems if not actual damage to your Arduino. Give the servos a separate power supply with servo GND connected to Arduino GND.

 

...R

 

Robin, the picture is just to show the board and main connections (also the pull-ups are missing). For my experiments, the Arduino board gets the power from an external 2Amps power supply at 12V, looking to the Arduino UNO scheme, there is a 5v regulator that can support up to 1Amp current, it is enough to debug the software. Anyway, for the layout application of the decoder, you are right: the servo power source must be external.

 

Regarding the pull-up, I used a +5V pull-up just because also Arduino as internal pullup resistors but they are not connected since the board launch the software and in the meantime the servos move!

Anyway, it is important that at the start-up the command line it is not in a floating state. I am sure that also retarding the power supply of servos will fix the problem.

 

 

ciao!

antogar

Edited by antogar
Link to post
Share on other sites

  • 2 weeks later...

Hello to everyone and Happy Christmas!

 

I received two Arduino Nano w/328 and I mounted one of them on a PCB breadboard for the SERVO DCC decoder:

 

 

Yqalil.jpg

 

K9bLwC.jpg

 

the board is 50x58mm and works fine!

 

ciao, Antonino

Edited by antogar
  • Like 4
Link to post
Share on other sites

  • 1 year later...

Hello all, first of all sorry to wake this topic up again...

 

But as I am finally ready to start building my layout I have a question about this setup:

 

Did anybody found a suitable solution for changing the frog's polarity?

 

I am pretty sure that I might have more questions once I start playng with this, but so far it still is the best setup I've seen!

Link to post
Share on other sites

Hi Simon, thanks for yout reply. 

 

I was initially thinking of using micro switches, the simplest and cheapest solution. But having noticed that these little relay boards are selling fairly cheaply on ebay I might go for this more elegant solution. I would love to see this in action though, do you know anybody that does it like that?

 

Thanks again, 

 

Ed

Link to post
Share on other sites

  • RMweb Premium

Hi Simon, thanks for yout reply. 

 

I was initially thinking of using micro switches, the simplest and cheapest solution. But having noticed that these little relay boards are selling fairly cheaply on ebay I might go for this more elegant solution. I would love to see this in action though, do you know anybody that does it like that?

 

Thanks again, 

 

Ed

i'm using cheep relay boards to switch polarity on this small project servos driven via a Ardunio Nano mounted on a breakout shield to power the servos, although I have cheated and switch the relays/servos by switches.  Could have made it  fully DCC but I though not worth the effort  ( although may be worth doing it as a test board) .   Track will be powered via a Ardunio uno + motorshield  using DCC++

 

Excluding hardware and servos the electrics via a certain site came in at about £15 not bad for a DCC layout :sungum:

post-1480-0-39520700-1457015281_thumb.jpg

 

post-1480-0-99721500-1457015307_thumb.jpg

 

 

Nick

Link to post
Share on other sites

Brilliantly interesting, Nick!

 

The two main reasons that keep me from using relays are wiring and simplicity (or crudeness if you prefer...).

 

In my mind (not very electronically educated) the micro switches can be wired directly from the DCC buswire, saving an awful lot of wires running back and forth between the control panel or arduino to the frogs. (see attached picture).

 

post-1474-0-80887100-1457018542.jpg

 

Either the servo is pressing the microswitch, causing it to close the Normally Open (NO) connection or the switch will be released going back to the Normally Closed (NC) connection. Either way (depending on what side of the servo you place the switch) will connect the frog to the right polarity. Please correct me if I am wrong and miss something!

 

With relays, I suppose it works slightly different, where you need an extra wire from somewhere to power the relay to make the switch. Doing this through the arduino will also take up connections, which could be used for something else. Having said this, a relay might be more reliable.
Again, not very electronically educated, correct me if I am wrong..

 

Am I wrong to think that these cheap relays needs to be powered constantly to keep the connection closed? Would that not burn out the coil?
In my quests on that certain site I found that latching relays are a lot more expensive..

 

Thanks again for all your feedback! I'll go and have a look on your blog now :D

Link to post
Share on other sites

  • RMweb Premium

Your  wiring is correct, using relays you replace the switch with the relay but you do need an additional wire to bring the relay to 0 volts.  The board I used is 5 volts and powered from the Ardunio power shield.  As for relay life most are quoted at 1000000 operations and a couple of pounds a board of 4 will not break the bank if they fail prior to scrapping the layout. Given the amps that DCC can deliver I prefer using a relay as they are designed to switch high loads.  Also as I model 2mm reliably switching of microswitches defeated me in the past and scuppered my GJCL layout.  I have used MERG servo boards and relay boards but to be honest its cheaper to buy ready made far eastern Ardunio clones, although they are slightly harder to set up.  I would advise that the relay n/c position should be used for the "main" road.   As I mentioned converting the unit to full DCC should be possible but at the moment other than a academic  interest holds no interest for me at the moment.  I attach the code I'm using for your interest  you may need to change the pins to match what ever board your using

 

 
#include <VarSpeedServo.h>
// Copyright2015 - SERVO SWITCH CONTROL - SSC - PaulDMV
// a maximum of seven servo objects can be installed on the nano
// a maximum of eight servo objects can be created
// only change ** lines (speedx, range_x_low and range_x_high)
// install the VarSpeedServo.h Library before use!
 
 
VarSpeedServo myservo1; // create servo object to control a servo, connected to pin 3 
int pos1 = 0; // variable to store the servo position 
int button1 = 4; // The button will be on Pin 4 
int speed1= 59; // **variable to store the servo speed
int delay1= 50; // sets the delay time to reach position
int range_1_low=  48; // **sets the lower limit
int range_1_high= 112 ; // **sets the uper limit
int range_1_diff= range_1_high - range_1_low; //diffence to go to position
 
VarSpeedServo myservo2; // create servo object to control a servo, connected to pin 5 
int pos2 = 0; // variable to store the servo position 
int button2 = 8; // The button will be on Pin 8 
int speed2= 59; // **variable to store the servo speed
int delay2= 50; // sets the delay time to reach position
int range_2_low= 75; // **sets the lower limit
int range_2_high= 123; //**sets the uper limit
int range_2_diff= range_2_high - range_2_low; //diffence to go to position
 
VarSpeedServo myservo3; // create servo object to control a servo, connected to pin 6 
int pos3 = 0; // variable to store the servo position 
int button3 = 18; // The button will be on Pin 18 
int speed3= 59; // **variable to store the servo speed
int delay3= 50; // **sets the delay time to reach position
int range_3_low= 70; // **sets the lower limit
int range_3_high=  85; //**sets the uper limit
int range_3_diff= range_3_high - range_3_low; //diffence to go to position
 
VarSpeedServo myservo4; // create servo object to control a servo, connected to pin 9 
int pos4 = 0; // variable to store the servo position 
int button4 = 11; // The button will be on Pin 11 
int speed4= 27; // **variable to store the servo speed
int delay4= 50; // **sets the delay time to reach position
int range_4_low=  135; // **sets the lower limit
int range_4_high= 164; //**sets the uper limit
int range_4_diff= range_4_high - range_4_low; //diffence to go to po
 
VarSpeedServo myservo5; // create servo object to control a servo, connected to pin 10 
int pos5 = 0; // variable to store the servo position 
int button5 = 13; // The button will be on Pin 13 
int speed5= 37; // **variable to store the servo speed
int delay5= 50; // **sets the delay time to reach position
int range_5_low= 82; // **sets the lower limit
int range_5_high= 100; //**sets the uper limit
int range_5_diff= range_5_high - range_5_low; //diffence to go to po
 
void setup() 
myservo1.attach(3); // attaches the servo on pin 7 to the servo object
pinMode(pos1, OUTPUT);
pinMode(button1, INPUT); 
digitalWrite (button1, LOW);
 
myservo2.attach(5); // attaches the servo on pin 8 to the servo object
pinMode(pos2, OUTPUT);
pinMode(button2, INPUT); 
digitalWrite (button2, LOW);
 
myservo3.attach(6); // attaches the servo on pin 9 to the servo object
pinMode(pos3, OUTPUT);
pinMode(button3, INPUT); 
digitalWrite (button3, LOW);
 
myservo4.attach(9); // attaches the servo on pin 10 to the servo object
pinMode(pos4, OUTPUT);
pinMode(button4, INPUT); 
digitalWrite (button4, LOW);
 
myservo5.attach(10); // attaches the servo on pin 10 to the servo object
pinMode(pos5, OUTPUT);
pinMode(button5, INPUT); 
digitalWrite (button5, LOW);
 
 
void loop()
{
 
if (digitalRead(button1) == LOW)
for(pos1 = range_1_low; pos1 < range_1_high; pos1 += range_1_diff) // goes from 85 degrees to 95 degrees in steps of 1 degree 
{
myservo1.slowmove(pos1,speed1); // tell servo to go to position in variable 'pos' 
delay(delay1); // waits x ms for the servo to reach the position
if (digitalRead(button1) == HIGH) 
for(pos1 = range_1_high; pos1>=range_1_high; pos1-=range_1_diff) // goes from 95 degrees to 85 degrees in steps of 1 degree
myservo1.slowmove(pos1,speed1); // tell servo to go to position in variable 'pos' 
delay(delay1); // waits x ms for the servo to reach the position 
}
if (digitalRead(button2) == LOW)
for(pos2 = range_2_low; pos2 < range_2_high; pos2 += range_2_diff) // goes from 85 degrees to 95 degrees in steps of 1 degree 
{
myservo2.slowmove(pos2,speed2); // tell servo to go to position in variable 'pos' 
delay(delay2); // waits x ms for the servo to reach the position
if (digitalRead(button2) == HIGH) 
for(pos2 = range_2_high; pos2>=range_2_high; pos2-=range_2_diff) // goes from 95 degrees to 85 degrees in steps of 1 degree
{ myservo2.slowmove(pos2,speed2); // tell servo to go to position in variable 'pos' 
delay(delay2); // waits x ms for the servo to reach the position 
}
if (digitalRead(button3) == LOW)
for(pos3 = range_3_low; pos3 < range_3_high; pos3 += range_3_diff) // goes from 85 degrees to 95 degrees in steps of 1 degree 
myservo3.slowmove(pos3,speed3); // tell servo to go to position in variable 'pos' 
delay(delay3); // waits x ms for the servo to reach the position
if (digitalRead(button3) == HIGH) 
for(pos3 = range_3_high; pos3>=range_3_high; pos3-=range_3_diff) // goes from 95 degrees to 85 degrees in steps of 1 degree
myservo3.slowmove(pos3,speed3); // tell servo to go to position in variable 'pos' 
delay(delay3); // waits x ms for the servo to reach the position 
}
if (digitalRead(button4) == LOW)
for(pos4 = range_4_low; pos4 < range_4_high; pos4 += range_4_diff) // goes from 85 degrees to 95 degrees in steps of 1 degree 
{
myservo4.slowmove(pos4,speed4); // tell servo to go to position in variable 'pos' 
delay(delay4); // waits x ms for the servo to reach the position
if (digitalRead(button4) == HIGH) 
for(pos4 = range_4_high; pos4>=range_4_high; pos4-=range_4_diff) // goes from 95 degrees to 85 degrees in steps of 1 degree
{ myservo4.slowmove(pos4,speed4); // tell servo to go to position in variable 'pos' 
delay(delay4); // waits x ms for the servo to reach the position 
}
if (digitalRead(button5) == LOW)
for(pos5 = range_5_low; pos5 < range_5_high; pos5 += range_5_diff) // goes from 85 degrees to 95 degrees in steps of 1 degree 
myservo5.slowmove(pos5,speed5); // tell servo to go to position in variable 'pos' 
delay(delay5); // waits x ms for the servo to reach the position
if (digitalRead(button5) == HIGH) 
for(pos5 = range_5_high; pos5>=range_5_high; pos5-=range_5_diff) // goes from 95 degrees to 85 degrees in steps of 1 degree
}
{ myservo5.slowmove(pos5,speed5); // tell servo to go to position in variable 'pos' 
delay(delay5); // waits x ms for the servo to reach the position 
}
 
 
}}}}} // 5x close bracket, one for each servo
 
​you   have reminded me I need to update my external blog  :nono:
 
Nick
Link to post
Share on other sites

Hi Nick, thanks for the explanation!

 

I figure you don't control your relays through the arduino?
What I liked about the code earlier in this topic is that it can be used both analogue with toggle switches and digital, in my case with my Multimaus. I just like toggle switches, ever since I saw the first model railway control panel as a child, and having the option of throwing a point directly from the Multimaus is just a nice extra!

 

If I understand your explanation correctly you power your relay banks from a 5V buswire (the same as what is powering your servo's, I guess.) and there is only one wire running back to the control panel to switch the relay. I'll have to look into this because I can probably live with one extra wire... Even though I still need to figure out a way to tie it in with the 'analogue/digital servo code' but that can't be too hard!

 

Can you please clarify what you mean by 'reliably switching of microswitches'? The scale wouldn't make a difference to this (I'm modelling in N) as the arm of the servo would be changing the switch (see picture)? Or is there something about microswitches that they don't always switch, causing shorts on the frogs?

 

post-1474-0-91425500-1457084348.jpg

Link to post
Share on other sites

  • RMweb Premium

Hi Nick, thanks for the explanation!

 

I figure you don't control your relays through the arduino?

What I liked about the code earlier in this topic is that it can be used both analogue with toggle switches and digital, in my case with my Multimaus. I just like toggle switches, ever since I saw the first model railway control panel as a child, and having the option of throwing a point directly from the Multimaus is just a nice extra!

 

If I understand your explanation correctly you power your relay banks from a 5V buswire (the same as what is powering your servo's, I guess.) and there is only one wire running back to the control panel to switch the relay. I'll have to look into this because I can probably live with one extra wire... Even though I still need to figure out a way to tie it in with the 'analogue/digital servo code' but that can't be too hard!

 

Can you please clarify what you mean by 'reliably switching of microswitches'? The scale wouldn't make a difference to this (I'm modelling in N) as the arm of the servo would be changing the switch (see picture)? Or is there something about microswitches that they don't always switch, causing shorts on the frogs?

 

attachicon.gifservo switch.jpg

You are correct the relays are switched by DPDT  switches other pole gives the input to the servo.   I like you enjoy the tactile approach of throwing switches and to be honest recoding to allow the ardunio to operate the relays did not seam worth the effort of the small board Im working on  perhap in the future if I ever go full DCC control.   What you propose to operate the toggles should work however I model 2mm and point bars move <1.5mm ! so reliable switching without over stressing the point tie bars is problematic at least for me, attempting to use them in the past either resulted in not switching the toggles or over stressing the point blades  :no:  As I say personal prejudice on my part as I know other 2mm modellers swear by them I just prefer a more robust less  mechanical solution.  If you look at the photos you can see the tie bar mechanism used ( 2mm Association part) and note how little travel is required.

 

Regarding the wire if the relays are powered via the ardunio they share a common feed so only one wire is required to complete the circuit obviously a second is required to the switch connected to the common 0 Volt.

 

I believe the circuit could be modified to full DCC even if by using a second board to using something like this http://model-railroad-hobbyist.com/node/19070  switching to 0v instead of powering LEDS.

 

Ardunios are amazingly cheap for what they can do and using two or more may save hours of pointless coding ( unless you like that sort of thing )  given at the moment just checked a nano is about £1.50 and a expansion shield £1.39  so a 14 function decoder could be built for less than the price of a pint, even less if you use a mini which offers 17 functions.

 

Which ever way enjoy what your doing and let us know your results

 

Nick

Link to post
Share on other sites

Its my view that directly pushing a tie bar in 2mm with a servo arm is a risky proposition.  If the servo twitches, the tie bar is over-strained.   Therefore I'd use some sort of mechanical drive from the servo which allowed fuller movement at the servo motor arm, and a smaller movement at the tiebar.   Examples would include the servo mount kits from MERG and numerous other similar arrangements which use a combination of flexible wire and a shift in the pivot fulcrum to control the throw.

 

Then, a microswitch can be mounted at the servo arm (like Ed's image), with the arm moving through a decent range, and the microswitch will be reliable in its operation. 

 

 

- Nigel  (another 2mm modeller). 

Link to post
Share on other sites

Great stuff!

 

I hadn't thought about the strain that a servo could put on the tie bar. Obviously a big risk.. I was however thinking of connecting the servos to the point similar to this picture:
post-1474-0-81861400-1457092175.jpg

(source: http://www.housatonicrr.com/const_journal_11.htm )

 

To me this looks similar to what the Merg mount does, which is allowing the servo to swing to its full reach but because the wire pivots in the small hole, the swing at the tie bar end is much less (ignore the long piece of wire on the point end, this is obviously going to be shorter once installed. see website above). You probably need to do a bit of experimenting to figure out the distance between the point and the small hole to get the perfect balance.

 

Other than that I was thinking of using some sort of stopper blocks to physically stop the swing of the servo in case it does decide to go mad.. This might strip the gears of the servo, but it's a lot cheaper to replace one servo than a code 55 point..

 

I've also seen other ways of linking the servo to the points that allow for the servo to move full swing without directly transferring the moved distance directly to the point (I hope you can follow what I'm saying as I can't explain it any clearer..).

 

Anyway, I have some components on order from the far east, I have had a Arduino Mega in the drawer for a while without it being used, so in a couple of weeks when all the stuff arrives I need to set up some sort of test and have a play with all this! I'll probably try the microswitch set-up first as that is a lot simpler to wire. If I find that this isn't reliable enough I need to work on a more sophisticated solution.

Thanks again for all your help and inspiration!

Ed

Edited by Kiwi_Ed
Link to post
Share on other sites

A minor comment on that picture is the wire may be a little fat and thus stiff.   There are two factors which can be used in such mechanisms, the position of the fulcrum to alter the throw, and the springiness of the wire.   If the servo was mounted vertically, rather than horizontally, the rowing action at the end of the rod will be reduced, this might be advantageous.

 

- Nigel

Link to post
Share on other sites

 If the servo was mounted vertically, rather than horizontally, the rowing action at the end of the rod will be reduced, this might be advantageous.

Forgot to say that in my comment. I was planning to mount the servo vertically. Not only would it reduce the rowing action (like that expression!), but in my opinion also easier to mount and combine with the microswitch.

 

I'll try to keep your comment in mind about the springiness of the wire when looking for suitable wire! Any suggestions?

I see 'piano wire' mentioned on several places, but what exactly piano wire is is a mystery to me.. If I look inside our piano at home, it's full of wires of different gauges!

Edited by Kiwi_Ed
Link to post
Share on other sites

  • RMweb Premium

Paul  Hodgson showed me a similar mount with the servo vertical which works for him, at the present time I will stick with omega loops although I have a idea of to "dampen" the servo movement but needs to be  a bit  clearer in my head what Im thinking   The other alternative I have considered is linear servos

 

 

post-1480-0-57697800-1457095486_thumb.jpg

 

 

NIck

Link to post
Share on other sites

Piano wire is a tough springy steel wire usually used in radio-controlled aircraft. Comes in different diameters, suggest 0.7mm as a starting place for turnout mechanisms. Probably related to the stuff used to string pianos at some time in the past.   You could take a few from the home piano, depends if anyone would notice :-). 

 

 

I don't think the linear servos offers much advantage over the rotary type.   The issue is still making a linkage which can allow full range of servo movement to prevent damage to the tiebar and turnout.   Omega loops can help with over-driving the distance, as can just winding the wire around the servo axis creating a spring which can tighten or slacken. 

 

 

- Nigel

Link to post
Share on other sites

Spot on Nigel. I set the address + 4 in my Multimaus and all works just fine. Hadn't realised this oddity of the Roco system. Might be able to accomodate it in the decoder software with a bit of research.

 

As to cost.  An Arduino Nano can be had for about £8 to £10 and the only extra required is an optoisolator, a diode and 3 resistors.  Total cost is probably about £12 or less.  This should control 6 servos and/or several Tortoise motors. Sounds a reasonable price given that it includes the DCC accessory decoder function and built-in servo calibration capability.  I haven't found a cheaper option but then again I haven't looked very far.

 

Cheers

Dave

 

. Each TORTOISE will draw 15-16 ma. at stall

 

 

each output pin of the Arduino can source  40mA of current, therefore each output pin can be a 5V  40mA  power supply if you wish, eg digitalWrite (13,HIGH); // 5V at upto 40mA,  or digitalWrite(13,LOW); //power off

Edited by Pandora
Link to post
Share on other sites

I am very glad to have re-awoken this topic, a lot of new and additional information!

 

There are so many benefits to using servos (with or without arduino) as point motors, cost being probably the biggest one!

I personally think Peco comletely missed the plank when they decided to price their servos higher than a conventional solenoid point motor. Anybody doing a bit of research into servos soon realises that the same can be achieved for a fraction of the price. Peco could have had a real winner on their hands if they had priced them cheaper than the solenoids... But that's a discussion for elsewhere!

 

Anyway, I'd love to hear more arduino accessory decoder stories!

Link to post
Share on other sites

I have a few things in mind.

 

Turntable already pretty much done

 

Points & signals for sure.

 

Interlocking, for points & signals, working properly. (Though a red led for each locked lever in the frame, rather than physical locking - but that's also possible, if expensive!)

 

Working AWS ramps - the bell or hooter being supplied as sound files on an SD card - reed switches under the dummy ramp, magnets on the loco, logic in the arduino.

 

Multi-function decoder for the dockside crane I've been scratch building for about 15 years.

 

Working rolling bascule bridge, with gates, lights, etc. And, potentially clever bit, switching the approach track supplies so they provide a DCC "stop all" command when the bridge is not locked in the closed position. Don't know quite how to do that yet...

 

Now, if we could just arrange a substantial lottery win, so I could stop wasting my time making others rich...

 

Best

Simon

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...