Control 16 turnouts using PCA9685 with CMRI Arduino JMRI control

Moving on with controlling turnouts with servos, thanks to a comment on my blog from Erik I have now been experimenting with PCA9685 cards which allow you to control 16 turnouts on each card only using two pins from the Arduino i2C communication. On the mega board this will be pins 20 and 21 and on the UNO this will be A4 and A5. The PCA9685 can be connected to other PCA9685 in a serial linkup allowing each card to be separately addressed and control 16 servos. Apparently you can connect up to 62 boards giving a possible 992 servo controls. Which means your only limitation is the JMRI CMRI 48 addresses and your power supply capacity.

For this blog I will be showing how I set this up to be controlled by JMRI using the CMRI library. For full information on the PCS9685, how to connect it up and set addresses and specifications you are best to check out the Adafruit web site here.

I will be demonstrating controlling two servos. The wiring will be as shown below. I will have two connected servos on the first two sets of pins. For addresses the first pins are address 0 (zero) and the second pins are address 1 and so on. The board address solder pads have not been touched so the board address will be 0 (zero).

For the Arduino code first we need to get the library from Adafruit and put it into your Arduino library directory. Click on the link below and download the zip folder. Open the folder and copy the content file "Adafruit-PWM-Servo-Driver-Library-master"  into your Arduino library folder which will be found in you Arduino folder probably in your Documents folder.

https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library

Now restart the Arduino IDE if you already had it running. If all has gone well you should now have the Adafruit PWM servo example at the bottom of the example tab.

For my code the top part we will include the wire, Adafruit_PWMServoDriver, CMRI and Auto485 library as shown below.

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

Next I will setup the CMRI and Auto485 the same as previous examples but will include the PWM servo setup with no numbers in the brackets as this will be a single board on address 0

#define CMRI_ADDR 1
#define DE_PIN 2
int Tbit[2];

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);

The setup will be as shown below

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

Finally the void loop. I will be reading the CMRI bit from JMRI as previously done

   Tbit[0] = (cmri.get_bit(0)); //TMU1
   Tbit[1] = (cmri.get_bit(1)); //TBD1

The If statements will be similar to the Servo control and PL-11 control as previously done but this time within the IF statement we will be sending out a signal to the PCA9685 to control the servos. This is done with PWM.setPWM(0, 0, 200); where the first number is the servo address, the second number is the board address (for this example board address is 0) and the last number is the position of the servo to be moved to.

   if (Tbit[0] == 1){
     pwm.setPWM(0, 0, 170);
     Serial.println("trow");
   }

The full code to control the two servos which will be connected to the first two sets of pins address 0 and address 1 is shown below

#include 
#include 
#include 
#include 

#define CMRI_ADDR 1
#define DE_PIN 2 
int Tbit[2];

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); 


void setup() {
  Serial.begin(9600);
  bus.begin(9600);
  pwm.begin();
  pwm.setPWMFreq(60);  // This is the maximum PWM frequency
  //pwm.setPWM(0, 0, 150);
  //pwm.setPWM(1, 0, 200);
 
}

void loop(){
   cmri.process();
   Tbit[0] = (cmri.get_bit(0)); //TMU1
   Tbit[1] = (cmri.get_bit(1)); //TBD1
   
   if (Tbit[0] == 1){
     pwm.setPWM(0, 0, 170);
     Serial.println("trow");
   }
   if (Tbit[0] == 0){
     pwm.setPWM(0, 0, 150);
     Serial.println("close");
   }
   if (Tbit[1] == 1){
     pwm.setPWM(1, 0, 100);
   }
   if (Tbit[1] == 0){
     pwm.setPWM(1, 0, 200);
   }
}

One last thing to mention. The power supply for the servos 5v does not have to come from the PCA card. On my layout I have a 5v power supply bus line going around the base board so I tap of this for the servo 5v power and send the one cable signal wire to the PCA9685. This reduces the wiring on the layout. Also the supply for the PCA9685 is 5v so can also come from the same 5V  supply instead of the Arduino. The only connection from the Arduino to the PCA card are the SCL and SDA cables, so you could move this PCA card closer to all your turnouts to be controlled and run the SDA SDL cables to the card again reducing more wiring.

Next will be setting up JMRI turnout table which will be the same as previous setups with a single bit steady state pulse. First open the turnout table as shown below.

Within the turnout table click on add a turnout button, ensure you have the CMRI selected as the system connection drop down selection box. Make the address 1001 which will be for CMRI bit 0. Give it a name and click create. Select 1 bit as we will only be using one address bit on JMRI and steady state so we will always have a 1 or 0 as a constant bit and not a pulse.

With this setup and all communication working with my Arduino and JMRI I can now control the servos with JMRI through the Arduino to the PCA9685.

 

 

4 Comment

  1. Patrick says: Reply

    Hey there, these blog posts and your videos have been really good. I’m going down the JMRI/CMRI/Mega route with a bunch of PCA9685 boards. I used your sketches as inspiration, but… I’m struggling on the first board to get turnouts 1011 and upwards to work (i.e. bits 10 to 15) Any clues?

    This is what I’m trying to run. Anything for servo 11/bit 10 upwards is commented out to get it to run servo 1-10 successfully…

    #include
    #include
    #include
    #include
    #define CMRI_ADDR 1 // what CMRI node is the Arduino running this sketch?
    #define DE_PIN 2 // ????????????
    #define LED01 7 // LED code left in to try running pca9685 and LEDs on same mega.
    #define button 3 // sensor for the LED

    int buttonState = 0; // set a variable to store the button state
    int Tbit[2]; // set variable for a Turnout bit number IS THIS THE ISSUE??

    Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); //setup the board address 0
    Auto485 bus(DE_PIN); // arduino pin 2 for DE and RE pins
    CMRI cmri(CMRI_ADDR, 24, 48, bus); // defaults to a SMINI with address 0. SMINI = 24 inputs, 48 outputs

    void setup() {
    // LIGHTS
    pinMode(LED01, OUTPUT);
    pinMode(button, INPUT_PULLUP);
    // SERVOS
    Serial.begin(9600);
    bus.begin(9600);
    pwm.begin();
    pwm.setPWMFreq(60); // This is the maximum PWM frequency
    pwm.setPWM(0, 0, 170); //Initial setting when start up
    pwm.setPWM(1, 0, 170);
    pwm.setPWM(2, 0, 170);
    pwm.setPWM(3, 0, 170);
    pwm.setPWM(4, 0, 170);
    pwm.setPWM(5, 0, 170);
    pwm.setPWM(6, 0, 170);
    pwm.setPWM(7, 0, 170);
    pwm.setPWM(8, 0, 170);
    pwm.setPWM(9, 0, 170);
    // pwm.setPWM(10, 0, 170);
    // pwm.setPWM(11, 0, 170);
    // pwm.setPWM(12, 0, 170);
    // pwm.setPWM(13, 0, 170);
    // pwm.setPWM(14, 0, 170);
    // pwm.setPWM(15, 0, 170);
    }

    void loop() {
    cmri.process();
    //LIGHTS
    digitalWrite(LED01, cmri.get_bit(47)); // Light on address 1048 in JMRI
    buttonState = digitalRead(button);
    cmri.set_bit(0, buttonState);
    //
    // TURNOUTS ON BITS 0-15 I.E. ADDRESSES 1001-1016
    Tbit[0] = (cmri.get_bit(0)); //Turnout01
    Tbit[1] = (cmri.get_bit(1)); //Turnout02
    Tbit[2] = (cmri.get_bit(2)); //Turnout03
    Tbit[3] = (cmri.get_bit(3)); //Turnout04
    Tbit[4] = (cmri.get_bit(4)); //Turnout05
    Tbit[5] = (cmri.get_bit(5)); //Turnout06
    Tbit[6] = (cmri.get_bit(6)); //Turnout07
    Tbit[7] = (cmri.get_bit(7)); //Turnout08
    Tbit[8] = (cmri.get_bit(8)); //Turnout09
    Tbit[9] = (cmri.get_bit(9)); //Turnout10
    // Tbit[10] = (cmri.get_bit(10)); //Turnout11
    // Tbit[11] = (cmri.get_bit(11)); //Turnout12
    // Tbit[12] = (cmri.get_bit(12)); //Turnout13
    // Tbit[13] = (cmri.get_bit(13)); //Turnout14
    // Tbit[14] = (cmri.get_bit(14)); //Turnout15
    // Tbit[15] = (cmri.get_bit(15)); //Turnout16

    // TURNOUT01, ADDRESS 1001, BIT 0
    if (Tbit[0] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    pwm.setPWM(0, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    }
    if (Tbit[0] == 0){
    pwm.setPWM(0, 0, 150);
    }

    // TURNOUT02, ADDRESS 1002, BIT 1
    if (Tbit[1] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    pwm.setPWM(1, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    }
    if (Tbit[1] == 0){
    pwm.setPWM(1, 0, 150);
    }

    // TURNOUT03, ADDRESS 1003, BIT 2
    if (Tbit[2] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    pwm.setPWM(2, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    }
    if (Tbit[2] == 0){
    pwm.setPWM(2, 0, 150);
    }

    // TURNOUT04, ADDRESS 1004, BIT 3
    if (Tbit[3] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    pwm.setPWM(3, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    }
    if (Tbit[3] == 0){
    pwm.setPWM(3, 0, 150);
    }

    // TURNOUT05, ADDRESS 1005, BIT 4
    if (Tbit[4] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    pwm.setPWM(4, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    }
    if (Tbit[4] == 0){
    pwm.setPWM(4, 0, 150);
    }

    // TURNOUT06, ADDRESS 1006, BIT 5
    if (Tbit[5] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    pwm.setPWM(5, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    }
    if (Tbit[5] == 0){
    pwm.setPWM(5, 0, 150);
    }

    // TURNOUT07, ADDRESS 1007, BIT 6
    if (Tbit[6] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    pwm.setPWM(6, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    }
    if (Tbit[6] == 0){
    pwm.setPWM(6, 0, 150);
    }

    // TURNOUT08, ADDRESS 1008, BIT 7
    if (Tbit[7] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    pwm.setPWM(7, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    }
    if (Tbit[7] == 0){
    pwm.setPWM(7, 0, 150);
    }

    // TURNOUT09, ADDRESS 1009, BIT 8
    if (Tbit[8] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    pwm.setPWM(8, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    }
    if (Tbit[8] == 0){
    pwm.setPWM(8, 0, 150);
    }

    // TURNOUT10, ADDRESS 1010, BIT 9
    if (Tbit[9] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    pwm.setPWM(9, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    }
    if (Tbit[9] == 0){
    pwm.setPWM(9, 0, 150);
    }

    //// TURNOUT11, ADDRESS 1011, BIT 10
    // if (Tbit[10] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    // pwm.setPWM(10, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    // }
    // if (Tbit[10] == 0){
    // pwm.setPWM(10, 0, 150);
    // }
    //
    // TURNOUT12, ADDRESS 1012, BIT 11
    // if (Tbit[11] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    // pwm.setPWM(11, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    // }
    // if (Tbit[11] == 0){
    // pwm.setPWM(11, 0, 150);
    // }
    //
    //// TURNOUT13, ADDRESS 1013, BIT 12
    // if (Tbit[12] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    // pwm.setPWM(12, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    // }
    // if (Tbit[12] == 0){
    // pwm.setPWM(12, 0, 150);
    // }
    //
    //// TURNOUT14, ADDRESS 1014, BIT 13
    // if (Tbit[13] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    // pwm.setPWM(13, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    // }
    // if (Tbit[13] == 0){
    // pwm.setPWM(13, 0, 150);
    // }
    //
    //// TURNOUT15, ADDRESS 1015, BIT 14
    // if (Tbit[14] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    // pwm.setPWM(14, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    // }
    // if (Tbit[14] == 0){
    // pwm.setPWM(14, 0, 150);
    // }
    //
    //// TURNOUT16, ADDRESS 1016, BIT 15
    // if (Tbit[15] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
    // pwm.setPWM(15, 0, 170); //set servo n (0-15), board n (0,1,2,3,4,5,etc), angle = n
    // }
    // if (Tbit[15] == 0){
    // pwm.setPWM(15, 0, 150);
    // }

    }

    1. admin says: Reply

      Hi Patrick
      The Tbit array should have the number or servos you will be recieving bits for, so for 15 bit addresses use Tbit[15], not sure why 0 – 9 work with your Tbit[2] so this might not be the issue.

      Try Tbit[10] with a serial.print within the if statement, will this print out on serial monitor to show the arduino is getting the bit communication from JMRI.

      if (Tbit[10] == 1){ //if Turnoutbit n == 1 (to throw) or 0 (to close)
      Serial.println (“this is Tbit 10”);
      }

      you will have to have the arduino connected to pc by USB cable and RS485

      Other than this the code seems to be ok. If this does not work strip the code down to just Tbit[10] test it and if still not working try posting it on Arduino forum and JMRI yahoo group. Lot people there that might help

  2. Patrick Maloney says: Reply

    Morning, cheers for the reply…
    At the moment it’s hooked up direct by USB so I won’t be able to use the serial monitor until my 485 bits arrive next week…
    I did wonder about the Tbit setting too. Will try what you’ve suggested about stripping out all but bit 10 and will see how I get on…
    Thanks!
    Patrick.

  3. Patrick says: Reply

    Well, the really odd thing is that it is bit 1 causing the issue.
    If I use bit 1 for a turnout, it stops any other turnout with a bit number starting with a 1 from operating i.e. 10 through to 15.
    The workaround is to reconfigure turnout 2 in CMRI using address 1017, i.e. bit 16. In this way the range of bits used is 0,2,3,4…16 and there is no issue.

    Very strange.

    Thanks for your help on this, and for the excellent videos!!

    Patrick.

Leave a Reply