Guidance request combining code from two sketches for slow motion turnouts controlled from jmri

First I'm new to Arduino. Have found a sketch that I have been using but have now hit a snag. The sketch which is intended to control servos on a model railroad was originally posted on the forum in Jan 22 by Iain_r and for the most part works. The problem I'm having is that only 4 servos (SG90) out of 8 servos connected to the PCA 9685 respond. I have defined “numServos ” to 8.

I have tested the servos and PCA9685 with Adafruit servo example and all work.

The Arduino Mega 2560 I’m using communicates with the controller (Pi running JMRI) with a library called CMRI. From the Pi I can see the correct CMRI traffic to tell the Mega to move servo 5 but the servo does not respond; 1 to 4 work as expected. Have replaced the PCA9685 and connected the PCA9685 to an UNO but it made no difference. PCA9685 is connected to a 5 volt power supply and the gnds are connected. Five volts to PCA is solid as tested with Fluke DVM.

From my testing I believe that the problem is with the sketch but after much reading and many changes I can not figure out where the problem lies.

/*
turnOp

This sketch operates a number of servos at a realistic speed taking the close (maxValue) and throw (minValue) commands from JMRI.
It also causes an associated relay to changes state as the turnout reaches its new position. This can be used for frog polarity switching, signals etc. 

The sketch is based on two sketches compiled by Rob at Little Wicket Railway (links below)

https://github.com/LittleWicketRailway/ServoControl/blob/master/Servos.ino
https://github.com/LittleWicketRailway/SlowMoServos/blob/main/MillisFunctionImproved.ino

Starting with the servoControl sketch, sections from the MillisFunctionImproved sketch were added.
Development was then carried out with a considerable amount of advice from Wildbill on the Arduino Forum.

This sketch is currently running on a test bench operating four servos via an Arduino Mega with a PCA9685

NOTE:
      minValve drives the servo right/clockwise, maxValve is left/anti-clockwise
      Thrown sets the turnout to the divergent route.  Closed sets the turnout to the straight route.

Iain Reeves Jan 2022
*/

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include <CMRI.h>
#include <Auto485.h>

#define CMRI_ADDR 1
#define DE_PIN  2
#define numServos 4                                        //The number of servos connected

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();   //setup the board address 0
Auto485 bus(DE_PIN);                                       // Arduino pin 2 -> MAX485 DE and RE pins
CMRI cmri(CMRI_ADDR, 24, 48, bus);

int Status[numServos];                                     //Create a table to hold the status of each turnout

//Create a table to hold the throw value for each servo
int minValue[numServos] = {1000,  //LY1(turnout ID) - enter values obtained from the Little Wicket calibration sketch
                           1100,  //LY2
                           1200,  //LY3
                           1300}; //LY4
                           
//Create a table to hold the close value for each servo
int maxValue[numServos] = {2000,  //LY1
                           1800,  //LY2
                           1900,  //LY3
                           2000}; //LY4

int CurrentPosition[numServos];             // table to hold the current positions of each turnout
int StepSize = 5;                           // if you require servos operating at varying speeds use an array here    int StepSize[numServos] = {5, 5, 3, 2};   to create a table to hold the step size for each turnout.  Also add [i] to each StepSize term.
int DelayTime = 15;                         // if you require servos operating at varying speeds use an array here    DelayTime[numServos] = {15, 15, 10, 5};   to create a table to hold the delay time for each turnout.  Also add [i] to the DelayTime term.

// Create a table to hold the output pin for each turnout relay
int Relay[numServos] = {36,      //LY1
                        38,      //LY2
                        40,      //LY3
                        42};     //LY4
                        
unsigned long previousMillis[numServos];    // table to hold the previous movement time for each turnout


void turnOp(int inputPin, int i)            //function to drive servos at slow speed
{
  unsigned long currentMillis = millis();
  
    if (((cmri.get_bit(i) != Status[i]) && (currentMillis - previousMillis[i] >= DelayTime)))       // compares CMRI signal against state of turnout and checks sufficient time has passed since last movement
  {
    previousMillis[i] = currentMillis;                                                              // updates last movement time
    if ((cmri.get_bit(i) == 1  ))                                                                   // checks if (cmri.get_bit(i)is 1
    {
      if (CurrentPosition[i] < maxValue[i])                                                         // then checks if current position value is less than maxValue - 'maxValue' used to account for L/R turnouts
      {
        CurrentPosition[i] = CurrentPosition[i] + StepSize;                                         // if both the above statements are true, moves turnout by one step (increasing)
        pwm.writeMicroseconds(i, CurrentPosition[i]);                                               // updates current position
      }
      else
      {
        Status[i] = 1;                                                                              // sets Status to 1
        digitalWrite(Relay[i], LOW);                                                                // de-energises relay associated with turnout [i]
      }
    }
    else
    {
      if (CurrentPosition[i] > minValue[i])                                                          // checks if current position value is greater than minValue - 'minValue' used to account for L/R turnouts
      {
        CurrentPosition[i] = CurrentPosition[i] - StepSize;                                          // moves turnout by one step (decreasing)
        pwm.writeMicroseconds(i, CurrentPosition[i]);                                                // updates current position
      }
      else
      {
        Status[i] = 0;                                                                              // sets Status to 0
        digitalWrite(Relay[i], HIGH);                                                               // energises relay associated with turnout [i]
      }
    }
  }
}


void setup()
{
  Serial.begin(9600);
  bus.begin(9600);
  pwm.begin();
  pwm.setPWMFreq(50);           // This is the maximum PWM frequency
  int i; 

  // centre servos when JMRI gives first command
  CurrentPosition[0] = 1500;    
  CurrentPosition[1] = 1500;
  CurrentPosition[2] = 1500;
  CurrentPosition[3] = 1500;
    
  // set up relay pins 
  {  
       for (int i = 0; i < (numServos); i++) 
    
  pinMode(Relay[i], OUTPUT);
  digitalWrite(Relay[i], HIGH);  
  }
}


void loop()
{
  cmri.process();
  
  for (int i = 0; i < (numServos); i++) 
  
    turnOp(2, i); 
}

Post an annotated schematic showing exactly how this is wired. Be sure to show all power, ground, power sources and note any wires over 10" in length. Post a link to technical information on each device including the jmri-945940.

Photo of current installation

Photo of schematic

Have looked for data sheet on LM2596 and TTL->RS485 but I can't find one and I can't remember where I sourced them? Found following on eBay site

Not sure what you are looking for with regards to JMRI. JMRI is model railroad software which in my case is running on a Raspberry Pi 4 8GB model. Currently I'm running JMRI version 5.4. CMRI library downloaded from GitHub.

@peterr69

welcome to the arduino-forum.

well done posting your code as a code-section in your very first post.

This not enough. The code uses multiple arrays which all must be expanded to 8 to make it work.

The code you have posted in post # 1 seems to the the original code written for 4 servos.

modifying code for arduinos is not just painting a different color on the box or adjusting a few knobs or dip-switches.
If the code is not designed to be fully parametrisised then you have to make changes to the code "by hand".
And this requires a pretty deep understanding of what the code does.

To use another analogy:
You are not only repainting a car, you want to remove the 4-cylinder engine and replace it with an 8 cylinder engine. Thsi requires more knowledge.

The following lines of code need adaption

#define numServos 4                                        //The number of servos connected
//Create a table to hold the throw value for each servo
int minValue[numServos] = {1000,  //LY1(turnout ID) - enter values obtained from the Little Wicket calibration sketch
                           1100,  //LY2
                           1200,  //LY3
                           1300}; //LY4
                           
//Create a table to hold the close value for each servo
int maxValue[numServos] = {2000,  //LY1
                           1800,  //LY2
                           1900,  //LY3
                           2000}; //LY4

how many turnout relays do you want to have?
zero? four? any other number?

// Create a table to hold the output pin for each turnout relay
int Relay[numServos] = {36,      //LY1
                        38,      //LY2
                        40,      //LY3
                        42};     //LY4
  // centre servos when JMRI gives first command
  CurrentPosition[0] = 1500;    
  CurrentPosition[1] = 1500;
  CurrentPosition[2] = 1500;
  CurrentPosition[3] = 1500;

These DC-DC-Step-down-regulators are a bit weak for 8 servos
image

chinese online-shops advertise them as 5A-regulators.
Well 5A is the 0,1 second maximum current they can stand but not as the permant load. Me personal I would only use these modules for a permanent load of 2A. For a higher amperage you would need adequate cooling of the regulator-chip.

Servos can easily draw 1A per servo maybe more. For 8 servos I would use a 10A power-supply.
What servos are you using? Have you looked up the stall-current of the servos? how much stall-current do they have?

The CMRI-library seems to send single bits for each turnout / each relay
If you want to change this from 4 servs / 4 relays to

8 servos this might work 1 to 1 (replace 4 relays with 4 servos) but all in all still having 8

If you want more than 8 things (servos or relays)
you will have to provide detail informations how these many bits are represented by the CMRI-library

I should have been clearer in my original post. I tend to take things slow and steady and connected the servos one at a time. This allowed me to modify the minValue and maxValue incrementally. Once I had 4 servos connected I modified the numServos to 5 and added lines to minValue, maxValue and CurrentPosition and Relay. That's when things stopped working.

In answer to your question I will not be using relays in his manner so will eventually be removing them from the sketch. But my thinking was let's get the servos working and then go back and clean up the rest.

I agree that the step down regulators are not capable of 5A continuous load. As these servos will be operating the turnouts on an infrequent basis, say 6 times an hour as a maximum, and will probably never operate all of the servos at the same time I thought that the regulators will probably be sufficient. In addition I will be adjusting the minValue and maxValue as precisiely as I can in order to limit the "stall" current of the servos. This has the added bonus of reducing "buzzing" noise on thee layout and should hopefully increase the life of the servos.

SG90 data

  • Running current (at no load) 400±30mA (4.8V ) 500±30mA(6V)
  • Stall torque (at locked) 2.0±0.20kg·cm (4.8V ) 2.2±0.20kg·cm(6V)
  • Stall current (at locked) 1300±40mA (4.8V ) 1600±50mA(6v)
  • Idle current (at stopped) 6±1mA (4.8V ) 6±1mA(6v)

You should post your attempt with using more servos and post your complete modifyied sketch.
WIthout seeing your complete sketch this is guessing in the fog

Here is the code that I am currently using. I have commented out any reference to the relays today and it has made no change. I don't think there is much change from the original sketch.

/*

This sketch operates a number of servos at a realistic speed taking the close (maxValue) and throw (minValue) commands from JMRI.
It also causes an associated relay to changes state as the turnout reaches its new position. This can be used for frog polarity switching, signals etc. 

The sketch is based on two sketches compiled by Rob at Little Wicket Railway (links below)

https://github.com/LittleWicketRailway/ServoControl/blob/master/Servos.ino
https://github.com/LittleWicketRailway/SlowMoServos/blob/main/MillisFunctionImproved.ino

Starting with the servoControl sketch, sections from the MillisFunctionImproved sketch were added.
Development was then carried out with a considerable amount of advice from Wildbill on the Arduino Forum.

NOTE:
      minValve drives the servo right/clockwise, maxValve is left/anti-clockwise
      Thrown sets the turnout to the divergent route.  Closed sets the turnout to the straight route.

Iain Reeves Jan 2022
*/

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include <CMRI.h>
#include <Auto485.h>

#define CMRI_ADDR 2
#define DE_PIN 2
#define numServos 5  //The number of servos connected
#define LED 13


Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();  //setup the board address 0
Auto485 bus(DE_PIN);                                      // Arduino pin 2 -> MAX485 DE and RE pins

CMRI cmri(CMRI_ADDR, 24, 48, bus);

int Status[numServos];  //Create a table to hold the status of each turnout

//Create a table to hold the throw value for each servo
int minValue[numServos] = { 1000,    //LY1(turnout ID) - enter values obtained from the Little Wicket calibration sketch
                            650,     //LY2
                            950,     //LY3
                            1120,    //LY4
                            1500};   //LY5

//Create a table to hold the close value for each servo
int maxValue[numServos] = { 1800,    //LY1
                            1700,    //LY2
                            1600,    //LY3
                            1750,    //LY4
                            1000};   //LY5

int CurrentPosition[numServos];  // table to hold the current positions of each turnout
int StepSize = 5;                // if you require servos operating at varying speeds use an array here    int StepSize[numServos] = {5, 5, 3, 2};   to create a table to hold the step size for each turnout.  Also add [i] to each StepSize term.
int DelayTime = 15;              // if you require servos operating at varying speeds use an array here    DelayTime[numServos] = {15, 15, 10, 5};   to create a table to hold the delay time for each turnout.  Also add [i] to the DelayTime term.

/*
// Create a table to hold the output pin for each turnout relay
int Relay[numServos] = { 36,    //LY1
                         38,    //LY2
                         40,    //LY3
                         42,    //
                         46};  //LY4

*/
unsigned long previousMillis[numServos];  // table to hold the previous movement time for each turnout


void turnOp(int inputPin, int i)  //function to drive servos at slow speed
{
  unsigned long currentMillis = millis();

  if (((cmri.get_bit(i) != Status[i]) && (currentMillis - previousMillis[i] >= DelayTime)))  // compares CMRI signal against state of turnout and checks sufficient time has passed since last movement
  {
    previousMillis[i] = currentMillis;  // updates last movement time
    if ((cmri.get_bit(i) == 1))         // checks if (cmri.get_bit(i)is 1
    {
      if (CurrentPosition[i] < maxValue[i])  // then checks if current position value is less than maxValue - 'maxValue' used to account for L/R turnouts
      {
        CurrentPosition[i] = CurrentPosition[i] + StepSize;  // if both the above statements are true, moves turnout by one step (increasing)
        pwm.writeMicroseconds(i, CurrentPosition[i]);        // updates current position
      } else {
        Status[i] = 1;                // sets Status to 1
        //digitalWrite(Relay[i], LOW);  // de-energises relay associated with turnout [i]
      }
    } else {
      if (CurrentPosition[i] > minValue[i])  // checks if current position value is greater than minValue - 'minValue' used to account for L/R turnouts
      {
        CurrentPosition[i] = CurrentPosition[i] - StepSize;  // moves turnout by one step (decreasing)
        pwm.writeMicroseconds(i, CurrentPosition[i]);        // updates current position
      } else {
        Status[i] = 0;                 // sets Status to 0
        //digitalWrite(Relay[i], HIGH);  // energises relay associated with turnout [i]
      }
    }
  }
}


void setup() {
  Serial.begin(9600);
  bus.begin(19200);
  pwm.begin();
  pwm.setPWMFreq(50);  // This is the maximum PWM frequency
  int i;
  int q;
  pinMode(LED_BUILTIN, OUTPUT);
  
  for (int q = 25; q < 30; q++) {  // Declaring pins 25 to 29 as inputs
    pinMode(q, INPUT_PULLUP);
  }

  // centre servos when JMRI gives first command
  CurrentPosition[0] = 1500;
  CurrentPosition[1] = 1500;
  CurrentPosition[2] = 1500;
  CurrentPosition[3] = 1500;
  CurrentPosition[4] = 1500;
  CurrentPosition[5] = 1500;

/*
  // set up relay pins
  {
    for (int i = 0; i < (numServos); i++)

      pinMode(Relay[i], OUTPUT);
    digitalWrite(Relay[i], HIGH);
  }
  */

}


void loop() {
  cmri.process();
  digitalWrite(LED, cmri.get_bit(25));  // bit CMRI bit 25 is JMRI bit 1026  Example bit 13 = 1014 in JMRI


  for (int i = 0; i < (numServos); i++)

    turnOp(2, i);
}

I forgot to note that the one change that I made to the code was in reference to the on board LED. I use that LED to prove that the Arduino device is communicating to JMRI by toggling it on/off from JMRI.

you only have 5 servos, but you set 6 values in this array. This affected the value of the Status array.

I have removed the last line and it has made no difference.

Last reply was unclear. I have removed the last line "CurrentPosition[5]" and it has not made a difference.

I've noticed something new. If I upload the sketch and through JMRI try to change the state of servo 5 it will move the servo once. Repeated attempts to change servo5 have no effect after the first attempt. I then added servo 6 to the sketch. If I change the state of servo 6 it will move once and further attempts have no effect. Attempting to change the state of servo 5 has no effect if I first change the state of servo 6. Conversely with servo 6 defined if I change the state of servo 5 it will react once but if I then try and change the state of servo 6 it has no effect.

The min value of servo 5 is 1500

int minValue[numServos] = { 1000,    //LY1(turnout ID) - enter values obtained from the Little Wicket calibration sketch
                            650,     //LY2
                            950,     //LY3
                            1120,    //LY4
                            1500     //LY5
                          };   

The max-value of servo 5 is 1000

//Create a table to hold the close value for each servo
int maxValue[numServos] = { 1800,    //LY1
                            1700,    //LY2
                            1600,    //LY3
                            1750,    //LY4
                            1000     //LY5
                          };   

I guess this makes no sense as a max-value should be bigger than a min-value
and this might explain why you see only a single movement

      if (CurrentPosition[i] < maxValue[i])  // then checks if current position value is less than maxValue - 'maxValue' used to account for L/R turnouts

but if CurrentPosition[i] is bigger than maxValue ==> no movement

As you can see from this example:
You have to understand what the code does.
Tinkering around without understanding is an endless game of guessings and experimenting where success would be pure luck

suggest you add some prints in turnOp() to report the states of Status[] and get_bit() to determine what is or is not happening

when i did this, i found that Status [4] what 1500, instead of 1 or 0

Please post your latest code - text descriptions of what changed are not sufficient.

What is the advantage of using a PCA9685 in this environment? The Mega is capable of controlling the servos directly, what makes things much more easy.
The MobaTools library can drive up to 16 servos, move them slowly and automatically switch the servos off when the turnout is in position - so it saves power.
The MobaTools library was originally written precisely for such applications :slight_smile: .

Am currently looking at the MoBaTools library. From first look it appears that it will do exactly what I want it to. My reasons for using a PCA9685 was that from my searches of the model railroad sites this approach was the most common and most successful. Until I read this post I was unfortunately not aware of the MoBaTools library. I assume there are no conflicts with MoBaTools and CMRI?