A reader on another thread had questions about running multiple turnouts on a small layout with an Uno. Primarily he wants to control turnouts with a control panel and feedback indicators. I thought that his needs are pretty typical for small layouts, and would be a good example for others planning and building on a similar scale.
So this post is bottom-up exercise in planning and implementing single UNO control on a small layout. I hope it will be helpful in planning and building your layout.
The Requirements
The layout has 5 turnouts that will be controlled with push buttons. Control is toggle-style: each button push causes the turnout to toggle between positions. Turnouts are run by micro servos. The control panel will have a layout map with turnout push buttons and LED position indicators.
First Decisions
The UNO is the first board many of us encounter and is certainly up to the task of managing 5 turnouts and a control panel, with capacity to spare. There are other Arduino boards suitable for model railroading use; but the UNO is cost-effective and easy to work with.
That said, the issue you have to contend with eventually is the number of connections required to support all the devices and LEDS that will be attached to the microcontroller.
Arduino boards provide three different types of connections/pins:
- Digital: digital pins are basic on/off binary connections. In output mode they are either fully on (emitting 5 volts, with a max current of 40 mA) or off. In input mode they read the incoming current as either HIGH (on) or LOW (off). When used as inputs, digital pins may need pull-up or pull-down resistors so that they will function correctly.
- Digital PWM: some, but not all, digital pins are also capable of PWM – pulse width modulation. The output of a PWM pin, if not fully on or off, is a series of pulses that cause the output to to on a percentage of the time. The pulses are used to control servos and other PWM devices. PWM with LEDS lets you vary their brightness between 0 and 100%, supporting a variety of interesting lighting effects.
- Analog: Analog pins are primarily used to read sensors that produce a variable output. Pin inputs are fed to the on-board ADC (analog-to-digital converter) for conversion to a number between 0 and 1024. Current and temperature sensors are example of analog devices read through analog pins / ADC. What you may not know about analog pins is that they can also be used as basic digital pins.
It is essential in early planning to determine all the devices to be connected to the microcontroller and the type of connection each requires to determine what connection issues you will need to resolve. In some cases, going to the UNO’s bigger cousin, the MEGA, will solve connection limits. But even the MEGA has limits. In some cases it makes sense to use pin-multiplying techniques using external chips to drive banks of LEDS, servos or other devices, even when you otherwise have enough pins.
A Connection Plan
The UNO has 14 digital connections, numbered 0 to 13, of which 6 (pins 3, 5, 6, 9, 10, and 11 ) are PWM. It also has 6 analog connections that can be used as digital connections with digitalRead() and digitalWrite() when referenced in the sketch as A0 through A5. That is a grand total of 20 digital pins, of which 6 are PWM.
Of those, two pins are generally off limits on an UNO: digital pins 0 and 1. They are Serial RX and TX respectively, and should be avoided for other uses on any UNO where you expect to use the USB interface — that would be most of the time. On boards without the built-in USB, pins 0 and 1 are fair game.
For five servos we need five digital pins. Even though PWM is used to control servos, the Arduino servo library creates the necessary pulses on any digital pin. However, using the library disables PWM functionality on pins 9 & 10, so some of the UNO’s native PWM capacity is sacrificed in any case.
For the five turnout control buttons we need five basic digital pins. For the control panel indicators, assuming one LED for each leg of each turnout, we need 10 basic digital pins.
That means we need 20 connections; but with pins 0 & 1 reserved for the USB interface, we’re short 2 connections. Many would want to use bi-color red/green LEDS so the state of each leg is continuously shown. That would require 10 additional basic digital connections.
Need Connections? No Problem!
That’s OK. Its exactly what I expected to happen when I started in on this exercise. Both inputs and outputs can be multiplied with external chips and boards; multiplying the basic digital outputs is far and away the easiest to implement, because the circuits are simple and the compiler includes native software support without add-on libraries.
I would use shift registers to control all the control panel indicators. That reduces the connection load on the UNO to just 3 connections for all LED indicators, no matter how many you end up with. With that, and the 10 pins needed to read the buttons and control the servos, the total digital pins requirement is 13. That leaves 5 available pins, one analog and up to three digital PWM, for other uses.
There is a great tutorial on controlling LEDs with shift registers, and chaining multiple registers together, on the Arduino website. That tutorial is mandatory if you are unfamiliar with shift registers; the circuit(s) shown are what you will be using for control panel LED indicators. The balance of this post assumes the basic knowledge contained in that tutorial.
Build A Control Board
Use a shift register chain to run your panel indicators. I use a TI 74HC595 chips that I buy from DigiKey. At less than $.50 each in lots of 10, they are a bargain. $5.00 worth of chips supports 80 outputs. Its the cheapest way I know of to extend the capabilities of an Arduino and run a lot of low current devices.
Decide how many shift registers you need — the 74HC595 has 8 outputs — and create a board based on the tutorial circuits to hold the chips and provide connection points for your LEDS. A single LED for each leg of each turnout (to show which leg is open to traffic) will need 10 connections—use two shift registers (use the two-chip circuit in the tutorial). Add a third chip (after the tutorial you will know how to do that) and your connection count rises to 24, enough to support red/green bi-color LEDS on each turnout leg on the panel. With 4 connections left, who knows what else you might light up?
Each push button will need a 10kΩ pull-down resistor connecting its pin to ground. The purpose of the pull-down resistor is to drain stray current and keep the pin LOW unless the button is pushed and full current flow is applied. See the diagram below. The resistor should be placed as close to the pin as possible.
Power Issues
While you can run one or two servos off the power supplied by the UNO’s +5 volt pin, more servos than that would exceed the board’s power handling capabilities. Accordingly, you will need to supply power to the servos from a separate power source. VERY IMPORTANT: the ground for the servo power source must be tied to UNO ground or the servos will not work correctly.
One Sketch To Run it All
Alright. The control panel is built and wired; the servos are installed (see this post for my latest methods) and connected to power and the UNO. What you need now is a sketch to run the control panel and the servos.
Here is a demonstration sketch to get you started:
////////////////////////////////////////// // // Small Layout turnout control // Demonstration Sketch // ////////////////////////////////////////// #include <Servo.h> // include the Servo library //////////////////////////////////////// // // Definitions // //////////////////////////////////////// //////////////////////////////////////// // Basic parameters; adjust for your actual setup #define NUMBER_OF_TURNOUTS 5 #define NUMBER_OF_SHIFT_REGISTERS 3 #define STEP_DELAY 70 // servo movement step delay, in milliseconds /////////////////////////////////////// /////////////////////////////////////// // Data Structures /////////////////////////////////////// ////////////////////////////////////// // TURNOUT_DEF holds all configuration // information about turnouts and panel LEDS ////////////////////////////////////// typedef struct TURNOUT_DEF { uint8_t button_pin; // Digital or analog pin for the button associated with this turnout uint8_t servo_pin; // Digital pin for the servo associated with this turnout int pos_main; // servo position for the MAIN leg, in degrees int pos_div; // servo position for the DIVERGENT leg, in degrees int panel_LED_main_green; // The position(s)of panel LEDS in the shift register chain int panel_LED_main_red; // Example assumes a bi-color (red/green) LED for each turnout leg int panel_LED_div_green; // modify these elements to reflect the actual LEDS you are using int panel_LED_div_red; }; ///////////////////////////////////// // TURNOUT_DATA is wrapper structure holding // both configuration and runtime data for turnout operation ///////////////////////////////////// typedef struct TURNOUT_DATA { TURNOUT_DEF data; // configuration bool is_moving; byte alignment; int pos_now; int target_pos; unsigned long last_move; }; // Alignment state values #define ALIGN_NONE 0 #define ALIGN_MAIN 1 #define ALIGN_DIVERGENT 2 // pin ids for shift register chain controlling panel LEDS #define LATCH_PIN 7 #define CLOCK_PIN 8 #define DATA_PIN 9 ////////////////////////////////////////// // // Global variables // ////////////////////////////////////////// ////////////////////////////////////////// // TURNOUT_DATA Array // * A0, A1, etc refer to analog pins which are used for buttons in this example // * Replace pos_main (93) and pos_div (117) with real values for each turnout // * LEDS are identified by their output position in the shift register chain; // the identifier is a number between 0 and (NUMBER_OF_SHIFT_REGISTERS * 8) - 1. // Example assumes LEDS are connected to shift register outputs sequentially // from the first output of first register. You can connect LEDS to any output in // any order; just set the identifiers accordingly. // // Only the TURNOUT_DEF part of the TURNOUT_DATA structure has to be initialized here; // The remaining elements are managed internally and are initialized automatically ////////////////////////////////////////// TURNOUT_DATA turnouts[NUMBER_OF_TURNOUTS] = { {{A0, 2, 93, 117, 0, 1, 2, 3}}, {{A1, 3, 93, 117, 4, 5, 6, 7}}, {{A2, 4, 93, 117, 8, 9, 10, 11}}, {{A3, 5, 93, 117, 12, 13, 14, 15}}, {{A4, 6, 93, 117, 16, 17, 18, 19}} }; // servo objects Servo servos[NUMBER_OF_TURNOUTS]; // array to hold shift register state bytes byte panel_LEDS[NUMBER_OF_SHIFT_REGISTERS]; void setup() { // Setup pins for shift register chain pinMode(LATCH_PIN, OUTPUT); pinMode(CLOCK_PIN, OUTPUT); pinMode(DATA_PIN, OUTPUT); // initialize each turnout for(int i = 0; i < NUMBER_OF_TURNOUTS; i++){ // attach the servo servos[i].attach(turnouts[i].data.servo_pin); // set the pin mode for the button pin pinMode(turnouts[i].data.button_pin, INPUT); // test and position the turnout by moving // to divergent then to main positions servos[i].write(turnouts[i].data.pos_div); turnouts[i].pos_now = turnouts[i].data.pos_div; setTurnout(i, ALIGN_MAIN); } } // end of setup void loop() { // get elapsed milliseconds at loop start unsigned long currentMillis = millis(); // loop through the turnouts array for(int i = 0; i < NUMBER_OF_TURNOUTS; i++){ if (turnouts[i].is_moving) { // if sufficient time has elapsed since the last move if ( (currentMillis - turnouts[i].last_move) >= STEP_DELAY ) { // move the turnout one degree turnouts[i].last_move = currentMillis; if (turnouts[i].pos_now < turnouts[i].target_pos) { // if the new angle is higher servos[i].write(++turnouts[i].pos_now); } else { // otherwise the new angle is equal or lower if (turnouts[i].pos_now != turnouts[i].target_pos) { // not already at destination servos[i].write(--turnouts[i].pos_now); } } } // if target position reached, stop turnout motion if (turnouts[i].pos_now == turnouts[i].target_pos) { turnouts[i].is_moving = false; turnouts[i].last_move = 0; setIndicators(i); } } else { // if a turnout is NOT in motion, check to see if its button is pressed int button_state = digitalRead(turnouts[i].data.button_pin); if(button_state == HIGH){ // toggle position if(turnouts[i].alignment == ALIGN_MAIN){ setTurnout(i, ALIGN_DIVERGENT); } else { setTurnout(i, ALIGN_MAIN); } } } } }// end of main loop //////////////////////////////////////////////////////////////// // Supporting Functions //////////////////////////////////////////////////////////////// void setTurnout(int id, int align){ // Set indicators to show turnout in motion turnouts[id].alignment = ALIGN_NONE; setIndicators(id); // Set values to trigger motion on next loop iteration switch(align){ case ALIGN_MAIN: turnouts[id].is_moving = true; turnouts[id].last_move = 0; turnouts[id].target_pos = turnouts[id].data.pos_main; turnouts[id].alignment = ALIGN_MAIN; break; case ALIGN_DIVERGENT: turnouts[id].is_moving = true; turnouts[id].last_move = 0; turnouts[id].target_pos = turnouts[id].data.pos_div; turnouts[id].alignment = ALIGN_DIVERGENT; break; } } void setIndicators(int id){ switch(turnouts[id].alignment){ case ALIGN_NONE: // means the turnout is in motion and not aligned panelWrite(turnouts[id].data.panel_LED_main_red, HIGH); panelWrite(turnouts[id].data.panel_LED_main_green, LOW); panelWrite(turnouts[id].data.panel_LED_div_red, HIGH); panelWrite(turnouts[id].data.panel_LED_div_green, LOW); break; case ALIGN_MAIN: panelWrite(turnouts[id].data.panel_LED_div_green, LOW); panelWrite(turnouts[id].data.panel_LED_div_red, HIGH); panelWrite(turnouts[id].data.panel_LED_main_green, HIGH); panelWrite(turnouts[id].data.panel_LED_main_red, LOW); break; case ALIGN_DIVERGENT: panelWrite(turnouts[id].data.panel_LED_div_green, HIGH); panelWrite(turnouts[id].data.panel_LED_div_red, LOW); panelWrite(turnouts[id].data.panel_LED_main_green, LOW); panelWrite(turnouts[id].data.panel_LED_main_red, HIGH); break; } } ///////////////////////////////////////////////// // Shift Register Functions ///////////////////////////////////////////////// void panelWrite(int id, byte state) { int reg = floor(id / 8); int pos = id % 8; bitWrite(panel_LEDS[reg], pos, state); panelRefresh(); } void panelRefresh(){ // Prepare to shift by turning off the output digitalWrite(LATCH_PIN, LOW); // shift all bits out in MSB (most significant bit first) order for(int i = (NUMBER_OF_SHIFT_REGISTERS - 1); i>=0; i--) { // shift out the bits shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, panel_LEDS[i]); } // turn on the output to activate digitalWrite(LATCH_PIN, HIGH); }
The sketch compiles into a compact package leaving plenty of memory and resources for additional functionality.
The main loop is built around a simple multitasking model, allowing you to control task timing and balance multiple competing tasks. In this case, the main benefit of this methodology is control of the movement rate of turnout servos. On my test loop this methodology allows block occupancy detection to work in the background along with signal logic.
Troubleshooting
If you build the circuits accurately, everything should work out of the box (as it were). I am an obsessive tester — so I test things at critical stages as I build them. Build and test your shift register circuits on a breadboard first, before soldering everything to a prototyping board.
Getting the connections to the UNO correct is key. If the LEDs don’t light at all, check all three connections, plus power connections; LEDS are polarized, so make sure the anodes are connected to incoming power and the cathodes to ground. Tracing power flow with a multi-tester should help you find problem areas quickly.
If your LEDs light but alternately flash odd/even, you probably reversed the LATCH_PIN and CLOCK_PIN connections.
For 3 or more servos, you must have an independent power supply to power them. BE SURE TO CONNECT GROUND FROM THE SERVO POWER SUPPLY TO ARDUINO GROUND.
Do Something with Unused Outputs
Old style hard wiring, without the help of a microcontroller, is a messy business at best, which tends to limit what you can do. DCC can help bridge the gap, but layout control is something of an afterthought for DCC (it was designed for locomotive control only) and is, frankly, awkward to use (my opinion; your mileage may vary).
To get the kind of functionality we have here without Arduinos — turnout control with a synchronous control panel — it would have to be hard wired. Or you could connect a full computer and run JMRI; that would require DCC (plus stationary decoders for the turnouts and a USB->DCC interface) to work . You would have to use stall-motor type turnout motors or add microcontrollers (Peco now sells a “smart” turnout system using servos and custom controllers) to interface with servo motors. So if you want to use servos instead of stall-motors, you can go Peco or do it the Arduino way from the start. As much as I appreciate Peco’s efforts here, the openness of the Arduino platform is a big part of what makes it useful and cost-effective.
Adding another function to a small layout — lets say a signal to protect a turnout — is a matter of connecting the signal leads to unused outlets and writing some code to run it. If you’ve gotten this far, you’ll spend far more time thinking about exactly how the signal should run in conjunction with the turnout it is protecting, than you will connecting the device and adding code to the sketch. Once you get into this way to doing things, I guarantee you’ll never look back.
So what will it be? Signals? A fire simulation (using PWM and LEDS)? A lighted structure? Let your imagination run wild!
UPDATE
I starting working with shift registers a while back. They are so effective my habit is to always use shift registers with LEDs, such as for signals, structure lighting and control panel applications. The habit is so ingrained that I forget to mention the other important reason for using them: power management.
The power handling capability of an UNO is limited to 40 mA per pin and 400 mA for the entire board. LEDs typically draw between 20 and 30 mA. So if you are directly powering 20 LEDs from an UNO, you are exceeding its power handling capacity and will burn out the board.
In the example given in the post, no more than half the LEDs well be lit at any one time so we are unlikely to exceed the 400 mA limit overall. A few more lit LEDS, though, would put it over the top. Shift registers duck the problem altogether because each register is powered—and supplies power to connected devices—independently. Other than the total load on your power supply, adding devices/lights to shift registers or even adding additional registers, puts no significant power load on the UNO.
Above I mentioned that an independent power supply is needed to run the servos. From a “best practices” perspective, one should always power external devices with an independent power supply. Arduino’s current handling limits are tight; always using external power supplies avoids that problem.
As of 2021, a commercial port multiplier/power management board is available for digital outputs! These boards, called duinoNodes, combine shift registers and power management components, providing the high current (up to 1/2 amp on a single port) digital outputs a model railroader needs. To find out more, go to Lew’s Duino Gear.
UPDATE
Several readers asked if I have a fritzing image of the 3 servo demo. I did not, but I put one together for those who might find this helpful.
This circuit is intended to work with the above sketch, with the turnouts variable shortened to 3 elements instead of 5:
TURNOUT_DATA turnouts[NUMBER_OF_TURNOUTS] = { {{A0, 2, 93, 117, 0, 1, 2, 3}}, {{A1, 3, 93, 117, 4, 5, 6, 7}}, {{A2, 4, 93, 117, 8, 9, 10, 11}} };
Always cross check all connections on a circuit like this against appropriate data sheets before powering it up for the first time! Be sure to connect the ground of your Arduino to the ground of your external power supply.
Hi Robin, Thank you for such an excellent & informative site, is it possible to download (or e-mail me) a larger image of your “Test sketch for 3 Servo’s on a breadboard ” on thenscaler/?p=942 . Many thanks Terry
See the update to the post above.
Hi Robin, thank you for a very informative article’ I am very new to Arduino but learning every day, I hope you dont mind my asking but have you got a Fritzing image of “To test the sketch for this post I set up a 3 servo simulation on a breadboard”, that you could post or email me. Regards Tony
See the update to the post above.
Hi Robin, I have read your article with great interest and have tried out your code to control 3 servos attached to an Uno using 3 push button switches. I especially like the way the program doesn’t have to wait until finishing a move before it starts another – very neat. Do you have any suggestions for integrating this with a pwm driver card over I2C. I would like to use one or more 16 channel driver cards based on the pca9685 (such as the Adafruit 16-channel-pwm-servo-driver) to control all the turnout servos on my layout. I would like to have a mimic panel with push buttons and leds with shift in registers for the buttons and shift out registers for the led indicators. I can see how to integrate the buttons and leds into the code but I’m struggling with how to control the servos. The driver uses the Adafruit_PWMServoDriver.h Library and not the Servo.h Library. The way that the commands are sent to the pwm driver to control servo position are in the form of setPWM(channel, on, off), this function sets the start (on) and end (off) of the high segment of the PWM pulse on a specific channel. You specify the ‘tick’ value between 0..4095 when the signal will turn on, and when it will turn off. Channel indicates which of the 16 PWM outputs should be updated with the new values. I have dialled in the appropriate servomin and servomax pulse lengths I need using the adafruit driver example sketches. My servomin and servo max postions will relate to pos_main and pos_div in your example.
Using the Adafruit driver, one does not attach a servo to a particular pin. In your example your TURNOUT_DATA array associates a particular button with a particular servo e.g. Button A0 controls the servo on pin 2. So I’m guessing I don’t need include the digital pin number in the array since this is only used to attach the servos to the pins – right?
Since the Adafruit driver doesn’t use the write command, I’m unsure how to incorporate the pwmset command and its arguments into the “loop through the turnouts array” section of the main loop.
Do you have any pointers or can you point me in the direction of someone who may have already done something similar to what I’m trying to achieve?
Any help would be appreciated.
Regards, Rick
Hi Rick,
As it happens I recently purchased and installed the Adafruit 16 Channel PWM board on the section of the layout currently under construction. What I like about the board is 1) it takes care of all PWM maintenance, freeing the microcontroller to do other things; 2) each port is individually addressable; 3) it uses the shared I2C bus and does not consume any pins; and 4) You can chain multiple boards together to expand the system.
While the Adafruit library for the board is different from the standard servo library, its pretty easy to adapt. The core issue in adapting it to sketches written for the standard servo library is deciding whether to express the desired position of a servo in degrees of arc (as you would with the standard servo library), or in the pulse length. If you stick to degrees (which I am doing for now), then the old call to servo.write() is converted to two lines:
pulselen = map(angle, 0, 180, SERVOMIN, SERVOMAX);
pwm.setPWM(servonum, 0, pulselen);
The first line maps the angle (which should be between 0 and 180 degrees), to a pulse length between SERVOMIN and SERVOMAX. Here I’m using the SERVOMIN and SERVOMAX as limiting terms so that we don’t try to set the servo outside its legal range (I think that is what the developers of the library had in mind). Since I use servos in the middle of their travel range–typically between 60 degrees and 110 degrees–I have not found it necessary to individually adjust the travel limits of servos; so I stick to Adafruit’s suggested default values. The second line sets the pulse length that was calculated by the map() function.
You can use the TURNOUT_DATA structure exactly as shown with one change — use the servo_pin element to represent the port on the board where the servo is attached, instead of using it to represent a microcontroller pin. Change the name to servo_port if like, and use it in your call to setPWM(), as you iterate through the turnouts array:
// loop through the turnouts array
for(int i = 0; i < NUMBER_OF_TURNOUTS; i++){ if (turnouts[i].is_moving) { // if sufficient time has elapsed since the last move if ( (currentMillis - turnouts[i].last_move) >= STEP_DELAY ) {
// move the turnout one degree
turnouts[i].last_move = currentMillis;
if (turnouts[i].pos_now < turnouts[i].target_pos) { // if the new angle is higher
pulselen = map(++turnouts[i].pos_now, 0, 180, SERVOMIN, SERVOMAX);
pwm.setPWM(turnouts[i].data.servo_port, 0, pulselen);
} else { // otherwise the new angle is equal or lower
if (turnouts[i].pos_now != turnouts[i].target_pos) { // not already at destination
pulselen = map(--turnouts[i].pos_now, 0, 180, SERVOMIN, SERVOMAX);
pwm.setPWM(turnouts[i].data.servo_port, 0, pulselen);
}
}
} ... continuing as in the original
The only other change I can think of is elimination of the pin attachment line in setup, since the board can be initialized staticly outside of setup and the loop, as Adafruit does in their example sketch. So with these few changes you should be able to adapt the demo sketch to your board.
If you want to eliminate the map() step, you can save target position information (pos_main, pos_div) as a pulse length, instead of degrees, and move the servo in the loop with one line like this:
pwm.setPWM(turnouts[i].data.servo_port, 0, ++turnouts[i].pos_now);
The advantage of going that route is that you can move the servo by changing the pulse length one unit at a time — which should result in very smooth, fluid motion by the servo. I suspect that is the optimum way to go.
I hope this helps. I’ll be blogging about this board as I refine my implementation, so stay tuned. Best, Robin
Robin, top man. That’s extremely helpful. I’m fairly new to messing about with Arduinos and coding and I knew that there had to be a simple elegant solution. I was trying to work it out in my head and got stuck in a loop somewhere. I’ve started to read up on how to code in C so that I can understand how the various functions, commands and data arrays etc. all fit together. Up to now I’ve been looking at other people’s sketches and trying to figure out what’s going on and trying to adapt them to my needs without a basic understanding of the language. So, I’ve ‘gone back to school’ if you like, to be able to understand why the code is written the way it is and so on. I’m a building physics consulting engineer by profession and my background is more mechanically biased. However, there is a small part of my brain that thinks it understands electronics and it’s trying to burst out and lend a hand with limited success. At least I haven’t blown anything up yet, although I did damage one of my servos slightly when I first fired up the servo driver with the example sketch without adjusting the pulse lengths and hadn’t switched my power supply from 12V to 5V. Luckily, when I took it apart the only thing broken was one of the small end stops which is set just beyond the normal 180 deg of travel to stop you mashing the gears. Reassembled it and it worked fine. I just need to remember not to drive it right to the end.
I really appreciate your input and look forward to reading your blog when you’ve implemented your system. I’ll let you know how I get on with my set up. Eventually I will have about 30 turnouts with servo control so I’ll chain at least two 16 channel boards with maybe a third for other moving accessories on the layout.
Regards, Rick
You are welcome. I look forward to hearing about your progress. Best, Robin
Hi Robin, well I had a go and integrated the code and it works great. I used the pulse length to define the pos_main and pos_div positions and the servos move very smoothly indeed. To move the servos I used the following:
pwm.setPWM(turnouts[i].data.servo_port, 0, ++turnouts[i].pos_now); //and,
pwm.setPWM(turnouts[i].data.servo_port, 0, –turnouts[i].pos_now); //to drive them back the other way
One thing I found was that the servos were a lot slower than before so I decreased the STEP_DELAY to about 30 ms and that was about right for me, bearing in mind that I model in OO gauge here in the UK so the turnout blades need to move further than in n gauge.
Next step – shift in registers.
Regards, Rick
Excellent. It makes sense that the servo would run slower when using pulse length to position them, so I’m not surprised you needed to adjust the delay.
Good luck with shift registers. I consider them a basic building block and could not imagine building an Arduino-controlled layout without them. If you need to control common anode devices, or high current devices, put a Darlington Driver chip between the shift register and the devices you are controlling – I use the ULN2803A chip from STMicroelectronics with my 5 volt shift registers.
Best, Robin
Guys,
have been trying to implement the changes you discuss here but just cannot get it to compile.
I have added the PWM adafruit library, changed servo_pin to servo_port // out the servo line
copied the scripts from above but get pages of errors..
Is there any chance you can post a skecth that covers this post please?
Robin, thanks for the tip. At the moment the shift registers will be just for button/switch inputs and indicator outputs but who knows where things will go. After much head scratching I managed to work out how to write and integrate the code to capture the button presses via the shift in register(s). I was especially impressed by the way the shift register/pin combination is addressed using the Floor and modulo functions (once I’d worked out what was going on with the help of a spreadsheet). I uploaded the software and set it to work. I only had 2 servos and 2 push buttons to hand but 3 sets of leds and initially button 1 didn’t do anything, button 2 was controlling servo 1 and servo 2 was wagging its horn back and forth. I checked my connections and then realised that I should have started the button numbers at 1 and not 0 in the TURNOUT_DATA array. This fixed the issue and buttons 1 and 2 controlled servos 1 and 2, respectively. The wagging servo horn had bothered me until I realised that I needed to ground out the unused input pins on the register. I plugged one of the servos into port 3 on the driver card and sure enough no more wagging and led indicating pos_main.
One thing that I had to modify is that I had to declare all the shift register functions before the setup and main loop code for them to be able to be called. The other supporting functions were able to stay where they were. This puzzled me as functions should be able to be declared before or after but a quick scan of the forums reassured me that I was not the only one to have similar issues.
My next step is to modify the code to accept multiple servo driver cards pwm1, pwm2 etc. I would like to have the pwm(n) address in the TURNOUT_DATA array and then reference it in the pwm.setPWM(turnouts[i].data.servo_port, 0, ++turnouts[i].pos_now); command. However, I tried that in the form of pwm(turnouts[i].data.pwm_adr).setPWM(turnouts[i].data.servo_port, 0, ++turnouts[i].pos_now); and the compiler didn’t like that and told me that the “‘pwm’ function not declared in scope”. I tried various ways of declaring it but couldn’t make it work. Looks like I’ll need to go back to head scratching for a while. I want to try to avoid adding multiple if else statements (and repeating code) to the main loop dependent on the turnout number to point at the corresponding pwm number.
Oh the joys of tinkering.
Regards, Rick
Instead of using shift-in, consider using a 4051 mux/demux IC for managing and polling multiple inputs. Here some basic info from the Arduino Playground: http://playground.arduino.cc/Learning/4051.
Why go that route? Its incredibly simple and there is no code overhead (no shiftin, shiftout or other specialized functions required). A 4051 mux/demux is basically an electronic version of a rotary switch. You use a 3 bit address (binary 0 to 7) to set the switch to point to a particular input/output then read or write to it as if the ultimate input/output were directly attached. So, assuming digital pins 2, 3, and 4 are the addressing pins (4 being the most significant bit), and pin 5 is the shared I/O connection to the 4051, the code to poll 8 buttons would look something like this:
for(int i = 0; i < 8; i++){ // set the address digitalwrite(2, bitRead(i, 0)); digitalwrite(3, bitRead(i, 1)); digitalwrite(4, bitRead(i, 2)); // read it int buttonstate = digitalread(5); // do something with the button state }
The switching process is, for all intents and purposes, instant so performance is excellent. Multiple 4051's can be stacked in an array for more complex switching operations. I am using 4051's to create switching fabric for accessing 24 CT current sensors in the high density part of my layout currently under construction.
As to the issue of function declarations, the Arduino IDE employs a relaxed syntax that does not require you to declare your functions in any particular place. However, if the compiler is confused by some aspect of the code, it will behave as if a formal declaration is missing. I've seen this behavior arise where there are unbalanced braces or missing semicolons -- but that is rare, since a more concrete error is usually flagged. One situation can arise, however, where the compiler will ALWAYS be confused with function definitions--- and that is where your function is redefining a library function. This happens if you give a function the same name as a library function. In that case, when the compiler encounters the function being used, it doesn't know which function definition to apply unless the function was previously declared to override the library function. So that is what I would look for if that particular error arises. Also, make sure your function calls include all required arguments, or that could confuse the compiler as well.
Best, Robin
I let your issue with multiple driver cards percolate a bit.
Try this-
Create a static array of driver objects before setup like this:
Adafruit_PWMServoDriver pwm[3];
In setup, initialize the drivers this way (assuming you’ve set the addresses with solder jumpers sequentially):
pwm[0] = Adafruit_PWMServoDriver(0x40);
pwm[0].begin();
pwm[1] = Adafruit_PWMServoDriver(0x41);
pwm[1].begin();
pwm[2] = Adafruit_PWMServoDriver(0x42);
pwm[2].begin();
Finally, store the array index of the applicable driver card (a number between 0 and 2) in the TURNOUT_DATA structure
turnouts[i].data.pwm_adr = 0; // for the first driver card
Now you should be able to control it this way:
pwm[turnouts[i].data.pwm_adr].setPWM(turnouts[i].data.servo_port, 0, ++turnouts[i].pos_now);
This could also be done with pointers rather than an array, but I think this is simpler.
Let me know if this helps. R.
Robin, yep that fixed it, brilliant. Many thanks. Simple and elegant as usual.
With reference to the shift in registers for capturing button presses. I’m quite happy with the way I’ve got it working with the CD4021 using the following code for the shift in register functions:
void buttonRefresh() {
//Pulse the latch pin:
//set it to 1 to collect parallel data
digitalWrite(SHI_LATCH_PIN, HIGH);
delayMicroseconds(20);
//set it to 0 to transmit data serially
digitalWrite(SHI_LATCH_PIN, LOW);
// shift all bits in, in MSB (most significant bit first) order
for(int i = (NUMBER_OF_SHIFT_IN_REGISTERS – 1); i>=0; i–) {
//shift in the bits
panel_buttons[i] = shiftIn(SHI_DATA_PIN, SHI_CLOCK_PIN, MSBFIRST);
}
}
int panelRead(int id){
buttonRefresh();
int breg = floor(id / 8);
int bpos = id % 8;
int bstate;
bstate = bitRead(panel_buttons[breg], bpos);
return bstate;
}
I will however have a play with the 4051 mux/demux IC to see how it goes.
Regards, Rick
That looks good to me. The 4051 is just another way to go about it. Since button/switch states are either high or low, that works well with a digital shift register. The 4051 can do the same things (albeit differently) — but really comes into its own when multiplexing analog sensors. R
Thank you, your article is very helpfull.
Note: The Fritzing image of the 3 servo demo does not show the Gnd connection of the servos power source to the UNO Gnd
You are correct. The ground link back to the UNO is not shown. R
Hi Robin,
I am very grateful for your sketch and explanations. I have successfully applied this to my own project with some minor modifications.
However I an unable to modify the case ALIGN_NONE. Instead of a steady red LED while the turnout is moving, I would prefer flashing red LEDs.
I considered applying the ‘Blink_without_Delay’ sketch but I can’t do it.
What would you kindly suggest?
That’s an interesting idea. Fortunately the main loop is a timing loop — like blink_with_delay — so it can be done.
Think of this as a repeat of the basic turnout motion problem — you have an array of turnout data that you step through and if a turnout is in motion and sufficient time has elapsed, you move it the next increment. Blinking panel indicators can be handled similarly; you’ll need to keep track of the timing and run through the panel data the same way you are handling turnout motion.
To make it work, you would set the turnout alignment to ALIGN_NONE when motion starts, then would change the alignment to MAIN or DIVERGENT after the motion is completed. My sketch does not work that way – it uses ALIGN_NONE only briefly to set (then forget) the panel indicators. You would need to add a panel_state (HIGH/LOW) & panel_last_state_change (unsigned long — millis() at last state change) elements to the turnout array.
Instead of trying to handle panel indicator blinking at the same time as you are handling motion, do a second pass through the turnouts array after the motion pass completes. This time you look for any turnout with alignment ALIGN_NONE, and if sufficient time has elapsed flip the state of the associated indicator.
Anyway, that’s my quick take on your issue. R.
Hello Robin,
Are you able to send me your sketch on how to handle multiple current sensors and signals at the same time without using the delay? I am at that point now. Thanks! – Scott
Hi Scott,
I’ve been trying to decide how best to help. There is a companion post http://thenscaler.com/?p=971 that talks about my signals framework, but doesn’t go into multi-tasking. For an introduction to my multitasking technique, see http://thenscaler.com/?page_id=661.
I’ve just started a github repository https://github.com/rpsimonds/thenscaler for sharing code. At the moment I have not posted what you need.
The code that I have that would tie it all together for you is the test loop code. However, I need to clean it up a bit before I share it. I will post an additional reply when the code is posted.
Best, Robin
Hello Robin,
Yes the test loop code would be the best for me to look at. Thanks Robin I await your test loop code.
I have placed the testloop code in the Testloop branch of my github repository: https://github.com/rpsimonds/thenscaler/tree/Testloop.
This is “intermediate” stage code in that it has been cleaned up and is lightly documented. Its not plug-and-play as a whole for obvious reasons (it should compile so long as you have the necessary libraries installed), but internal functions such as current sensing functions are readily transferable to other sketches. What should be fairly easy to discern overall is the structure of the code and my solutions to basic problems.
Let me know if you have any questions. R.
Robin,
I followed connections on the above fritzing diagram and copied your code making adjustments to the TURNOUT Data (5 to 3). I tested and found that the first LED always stays on. All other shift registers cycle correctly with each button push. What else do i need to do to make the first LED correctly sequence?
Thanks Ken
If I’m understanding correctly, you reproduced the 3 servo demo circuit and now the first green led just stays lit?
Technically that means that every time you shift the led bits out, that first bit is staying high. As to why that might be happening, I’m not sure. Post back your TURNOUT_DATA array so I can see how the data is setup.
In the statement “TURNOUT_DATA turnouts[NUMBER_OF_TURNOUTS] = {
{{A0, 2, 93, 117, 0, 1, 2, 3}},
{{A1, 3, 93, 117, 4, 5, 6, 7}},
{{A2, 4, 93, 117, 8, 9, 10, 11}},
{{A3, 5, 93, 117, 12, 13, 14, 15}},
{{A4, 6, 93, 117, 16, 17, 18, 19}}
};”, What are 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 in the above?
The answer is in the typedef for TURNOUT_DATA:
typedef struct TURNOUT_DEF {
uint8_t button_pin; // Digital or analog pin for the button associated with this turnout
uint8_t servo_pin; // Digital pin for the servo associated with this turnout
int pos_main; // servo position for the MAIN leg, in degrees
int pos_div; // servo position for the DIVERGENT leg, in degrees
int panel_LED_main_green; // The position(s)of panel LEDS in the shift register chain
int panel_LED_main_red; // Example assumes a bi-color (red/green) LED for each turnout leg
int panel_LED_div_green; // modify these elements to reflect the actual LEDS you are using
int panel_LED_div_red;
};
The elements in question correspond to the LED elements of the typedef.
Hi Robin,
I got it all working. I forgot to change the “NUMBER_OF_TURNOUTS” to 3 and “NUMBER_OF_SHIFT_REGISTERS” to 2. Thanks so much for this code. I plan to get the MEGA and expand to 16 turnouts! My plan should work given I make the adjustments in the code correct? Anything I should be aware of?
You are making my dreams come true!
Ken
Robin,
Impressive coding ! I installed the “demo” for 5 turnouts and it worked right out of the box(once I switched button and servo connections….).
So far I just enjoyed the smooth running, will now connect the first real installation.
Interesting material about mounting servos; I have installed several SG90 servos simply lying on their side, screwed to a plastic 90 degree angle, with the rotating arm away from the layout. Then music wire up through a hole in the plastic frame and up to the turnout.
The sketches I’ve tried before have been unstable, with jerky servos, will see how this performs in real life.
One actual question; when I turn on power the servo(only one installed, testing) jerks a short bit, then slowly moves to one position. Any ideas on how to fix that ? It’s the first jerk
that is the problem, assume it should go to one of the positions on upstart ?
Regards
Peter
Hi,
Glad you are having good results.
The jerky thing is the consequence of starting up PWM for the first time. These cheap little servos don’t buffer the signal in any way – they just respond when it starts up.
Motion smoothness is a function of the number of steps, the size of each step and the delay factor between each step. The servo library limits granularity to 360 steps per revolution which is somewhat noticeable if the delay factor isn’t just right.
You might try using a PCA9685 card for your PWM. See this blog post: L&NC Update; Running Lots of Turnouts. The advantages are 1) less jerkiness when the servo is energize; and 2) finer granularity of about 1024 steps per revolution.
Best, Rob
Hi,
Thanks,
I agree these cheap servos are, well, simple; but low cost. I am thinking along another line,
what if I simply delay power to the servos till after PWM is activated ? It could still jerk a little, but the PWM signal would be stable. PCA9685 is also interesting, will look further on that.
Regards
Peter
Try both and see what works best.
Hi,
Yes, I have good results, but the ever present “but” showed up. As I wrote before I have
the 5 servo sketch on a ProMini, on a breadboard. All servo pins work perfectly(except for, sometimes, that little jerk on power up). I also tried 3 servos connected, still fine. Now I tried a servo installed on my layout, ProMini still on the breadboard, on my workbench. This made the power cables 4 meter, and the signal cable 3 meter. When testing that, the servo went mad, running around totally out of control. Testing with another servo, working well at the bench test, gave the same result.
Have you by any chance seen something like that ? A first guess is of course voltage drop, so either the 5 volts or the control signal is too low. Maybe these simple servos just can’t stand that ? And, the Arduino sketch reasonably wouldn’t freak out based on load ?
Writing this I also recall your comment on Arduinos driving servos; My first tests with one servo was ok, adding just another resulted in the same thing. And 5 volt power was only about 4 volts.
Perhaps your suggested PCA9685 would help, if nothing else it should work as a pulse shaper/amplifier.
So, just checking if I could have missed something else….
Any thoughts on this are most appreciated.
Regards
Peter
I think the PCA9685 will generally improve results.
However, I think your wire runs may be too long. I wouldn’t run a servo cable more than say 75cm, or a meter at absolute most. I suppose you could overcome wire resistance with very heavy gauge wire, but I don’t think I’d go that route.
Best approach is to break layout tasks down and use multiple microcontrollers instead of trying to have one do everything. I’ve starting blogging about this technique. See Building Blocks for Layout Control and Basic Layout Control Nodes.
More coming on that topic soon. Best, Rob.
Thanks,
Good to hear I may have a solution; panicked a little yesterday. Longest distance between turnouts on my present build is 1.5 meter, so it has to be around 0.75 m minimum signal cable length. Will have to try that, more than control unit feels like overkill for four servos.
On the other hand, you point out many interesting ideas about lightning and other things that need more pins; Think a fast clock which slowly lower the daylight in “evenings”, with corresponding illumination in houses, streetlights and so on.
I will follow this series, for sure !
Best Regards
Peter
Hello,
You comment” In some cases it makes sense to use pin-multiplying techniques using external chips to drive banks of LEDS, servos or other devices, even when you otherwise have enough pins.”
How would the sketch above be modified to move the servos to a a PCA9685. I am not a programmer so would you be so kind as to provide the code
See this post L&NC Update; Running Lots of Turnouts. The only code that changes is servo movement. Everything else stays the same.
Hi Robin. I am a complete novice to electronics and Arduino. I copied your sketch for controlling servos and LEDS, using shift registers. Code compiled OK to an Arduino Uno R 3 and downloaded successfully but nothing works. Servo moves to one position when switched but doesn’t return. I am using on / off toggle switches instead of push buttons. Is this the problem or am I missing something?
I would appreciate a reply
Best wishes
Danny
Hi Danny,
I’m going to guess that the switches are the problem. Assuming no other changes to the code, if you use push buttons instead, it will work.
Why is that? That problem with buttons and switches is that you have incompatible choices for how to act on them. You can act on buttons either when a push is sensed (the pin goes HIGH), or when it is released (the pin goes HIGH then LOW), OR you can act on both transitions — doing something when it goes LOW to HIGH, something else when it goes HIGH to LOW. Switches can be handled identically, or so it would seem.
Reread the above. Notice the focus on state transitions. The difference is time and embedded assumptions in the code. The highly simplified code on this post ASSUMES that the HIGH state will always be MOMENTARY, and it chooses to act on the HIGH state — but more complex code would act on the transition from LOW to HIGH. You can drill down to one line that encapsulates the issue:
if(button_state == HIGH){
When a switch goes HIGH, this line keeps executing over and over and ….. get it? You never escape until you change the switch position, which causes a different part of the code to execute. The way the code is written, the HIGH state is supposed to be momentary trigger that causes the turnout to change its state. Everything else happens in loop(). Since you keep resetting the turnout while the pin is HIGH, the turnout never gets a chance to move.
This is a good example of how hardware changes make code unportable. Whether on this site or elsewhere, specific hardware choices frequently do matter.
Best, Rob
Hi Robin
Thank you for your prompt and concise reply. This all makes sense even to me. I will try using button switches as you suggest and hopefully all will work as per. I will let you know how I get on.
Thanks again
Danny
Hi Robin,
Like one or two others, I’m a novice to this lark, I’ve been playing around with arduinos for a while & got a few things working fine, so not a total dumbo.
Anyway, I’m in the planning stages of a new layout, HO-9 so same track gauge, and want to incorporate what you kindly done here. My new layout only involves 5 turnouts so this is ideal.
My problem is trying to get the shift register connections correct? Your fritzing image is for 2 registers but the full 5 turnout “system” requires 3. As the sketch is for 5 turnouts I assume it’s merely a case of connecting another register, but what are the connections for this please?
Is there any chance you could do a wiring/Fritzing image for the full 5 turnout setup please?
I know this means more work for you but it would be very appreciated if you could.
Thank you for any help you can give.
Yours Keith Lawrence.
Hi Keith,
To understand how to use and interconnect multiple shift registers, do this tutorial: https://docs.arduino.cc/tutorials/communication/guide-to-shift-out. Lots of illustrations; I couldn’t do it any better. If you still have questions after going through that let me know.
Adding the second shift register sets the pattern that you follow for each subsequent shift register: Data Out from the previous shift register connects to Data In of the next shift register. That’s the serial connection. Two other logic connections are shared: Clock & Latch. That means every shift register connects to to same source for those two connections. That leaves Power, GND, OE (connect to GND) and the 8 output pins.
Best, Rob
Hi Robin,
Thanks for the prompt reply. I “studied” that “article” as you linked to it in your original blog. What confused me is that the breadboard image shows the second register connected as per you have, but the wiring diagram shows both terminals 1 & 2right hand side top connected to +5v whereas the image & your excellent article use the 2nd terminal rh top as a feed out to an led?
Hope I’ve explained myself properly?
This “setup” you have so kindly worked on, inc the sketch, is exactly what I need for my upcoming layout.
Thank you for your time and efforts;
All the best, Keith
Hi Keith, I had some trouble understanding where the issue is. I think the problem is inconsistency in the way the chip is presented in the tutorial schematics. Here is a correct representation of the physical chip and the “gates” assigned to each pin (note the pin 1 reference mark on the upper left):
Pin 16 (upper right) is Vcc; Pin 15 (upper right, below 16) is Q0, the first output port. All of my images stick to actual physical layout. In the tutorial schematics, they took the liberty of rearranging the gates on the component model, so that all the output gates are represented on the left side of the chip (the first one being on the right is a pain, so they changed the image). This creates the appearance of a difference.
Use the physical map as your guide and rely on pin numbers for resolving inconsistencies. The pin numbers are all correct on all drawings, but in the Arduino schematics they redrew the chip for convenience. Arduino designs always rearrange IC gates to suit the designer. I think its a lousy practice that creates unnecessary confusion. One of the few things they do I think is rather dumb.
Best, Rob
That’s great, thanks for clearing that up. As a novice with Arduino I really appreciate what you do to help myself & others. Thanks again.
Keith
Rob – Thank you for your very useful web pages. I am planning on using my Arduino (with your coding) to run multiple servos to control turnouts. To facilitate this I will install the PCA 9685 PWM driver boards. Do I need to have a motor control board in addition to the PCA 9685?
Thanks,
Dave
The PCA9685 board is all you need for servos. Best, Rob
Thank you. I have a follow-up question please. In using the PCA 9685 to control multiple turnouts, how do I modify the TURNOUT_DATA pin number to point it to the correct pin number on the 9685? For instance, in your code (below) for turnout data I see that the Arduino pin id for this turnout is 8. How do I modify this when using the 9685? I saw an associated post on June 7, 2020 by Peter but I am not sure if I understand it.
Thank you so much for your help.
TURNOUT_DATA turnout[NUM_TURNOUTS] = { {{8, 93, 117}, false, ALIGN_MAIN, 94, 94, 0}
};
Hi Dave,
Use the servo_pin member, but instead of putting an Arduino pin number in you are putting in the pin/port you are using on the PCA9685, which will be a number between 0 and 15. Since you are only doing 8 turnouts, you don’t have to address multiple boards so you don’t have to identify both the board and the port. The difference is in how you handle the port when you have to do something.
This post — C++ Objects for Layout Control, Part 2 — Turnouts — has what you need. The example is set up for PCA9685, but it also shows you how to use compiler directives to select either PCA9685 or the Servo Library without having to modify the turnout object. There is a link to the object code at the end of the post. If you are not familiar with object oriented programming, check out the first post of the series for a quick and dirty intro to OOP: C++ Objects for Layout Control, Part 1
Best, Rob
Thank you Rob for the informative info which I have reviewed and studied. I have a few more questions, please. For context, I have started with the Testloop sketch which includes the defines.h, standard_types.h files and have now included the turnout.h file. Here are my questions. Thank you so much for your help.
1. In the turnout.h file there is a typedef struct TURNOUT_PARAMS variable. Is this variable in addition to the typedef struct TURNOUT_DEF and TURNOUT_DATA variables in the standard_types.h file (from the Testloop file)? Or, does the standard_types.h file need editing? I suspect that the TURNOUT_PARAMS is a new variable for use in the Class code and no editing is required for the standard_types.h file.
2. The turnout.h file has three defines at the top (ALIGN_NONE, MAIN and DIVERGENT). The defines.h file (from Testloop) has these defines, except for the new ALIGN_NONE define. Can I amend the defines.h with ALIGN_NONE and delete those three from turnout.h?
3. In the Testloop file you declare TURNOUT_DATA in the form of an array:
{pin, position_main, position_divergent, is_moving, alignment, position_now, target_position and last_move}
What values should be selected for position_now, target_position and last_move? For instance, what is the basis for making this determination? I understand the units are either degrees or ‘ticks’ if you are using the servo library or Adafruit PFM Driver board, respectively.
4. Is it required to set the servo frequency anywhere in the code? I will be using the Adafruit PCA9685 Driver.
Once again, thank you so much!
Hi – Over time I used different techniques to do the same thing, so sometimes things don’t seem consistent.
Use the class code file set and all the defs should be present. If you need to add something, by all means do so. The TURNOUT_PARAMS structure is what you should use with the class code.
position_now, target_position and last_move are all dynamic properties set within the code. Study the set() and update() methods to understand how the values are used and altered.
position_now (or pos_now in some versions) is the current actual position of the servo, in ticks.
target_position is the current target of a moving servo, in ticks. Its value will equal either pos_main or pos_div. Look at set() to see it set.
last_move is the time in milliseconds (since system start) when the last move occurred. This property is used in update().
The basis for all calculations are the values you provide via TURNOUT_PARAMS: the port, the servo positions for MAIN (CLOSED) and DIVERGENT (THROWN), the default alignment for startup and the desired movement delay factor in milliseconds.
Your values for the MAIN and DIVERGENT positions will be between 150 (0 deg) and 650 (180 deg) ticks; the midpoint of travel (90 deg) should be 400 ticks. Here is an example data array:
#define MOVEMENT_INTERVAL 20
#define NUM_TURNOUTS 9
TURNOUT_PARAMS tdef[NUM_TURNOUTS] = {
{0, 375, 310, ALIGN_MAIN, MOVEMENT_INTERVAL},
{1, 345, 408, ALIGN_MAIN, MOVEMENT_INTERVAL},
{2, 337, 370, ALIGN_MAIN, MOVEMENT_INTERVAL},
{3, 288, 345, ALIGN_MAIN, MOVEMENT_INTERVAL},
{4, 340, 408, ALIGN_MAIN, MOVEMENT_INTERVAL},
{5, 309, 378, ALIGN_MAIN, MOVEMENT_INTERVAL},
{6, 291, 373, ALIGN_MAIN, MOVEMENT_INTERVAL},
{7, 355, 285, ALIGN_MAIN, MOVEMENT_INTERVAL},
{8, 305, 372, ALIGN_MAIN, MOVEMENT_INTERVAL}
};
Getting those numbers is trial and error, and depends on how you orient the servo and its arm. You might start by creating a simple sketch for testing servo position settings. You’ll quickly develop a feel for positioning.
Best, Rob
Thanks Rob for the information. Your response helps clarify a few items. I see now that I am mixing up two sketches. Although the OOP of C++ looks elegant and succinct, I am going to stick with C. I’m more familiar with it and it will suit my limited needs. Therefore, I will be referencing the Testloop files and not the turnout.h file. I intend to control turnouts only – no signals, block detection, etc. I do have a few more questions, please:
1. Since I will be using the PCA9685:
a. do I need to add the hardware interface code (lines 130 – 141 of turnout.h) to my Testloop sketch?
b. do I need to include the native Servo.h library?
2. I plan to use buttons and indicator LEDs with my design. In your page entitled “Turnout Control with Arduino and Servos”, there is a thread from Colin dated October 23, 2016. One of your posts (October 23rd) mentions a full example. Is there a way you could point me to this example code please? I think this would help me.
Thank you so much for your generous support.
Dave
Hi Dave,
1. The hardware interface is a place you can put and isolate your hardware specific code. Its a good practice that leads to clean, maintainable code. Do this even though you are using only one hardware type.
2. You don’t need to include the native servo library if you are not using it. Just include the PWM library.
3. Running a Small Layout with an Uno is a very small scale example that brings together turnout code and control panel buttons and LEDS. Its an early, non-OPP turnout implementation. Converting it to the oop version would not be too diffcult — mainly you have to remove most turnout code in loop(), and use the update() method to run the servos.
Best, Rob
Hi Rob
Thanks for your reply. I’m getting there….On a different subject I noticed in your Testloop file in your TURNOUT_DATA you increased the pos_now and target_pos by 1 degree over the pos_main values. For example, pos_now is 94 and pos_main is 93. Why is that? Here is your code:
// turnouts
TURNOUT_DATA turnout[NUM_TURNOUTS] = {
{{8, 93, 117}, false, ALIGN_MAIN, 94, 94, 0}
};
Thank you for your help.
Dave
I’m not sure exactly which post that came from, but anything involving the testloop was early experimental work. Might be a typo that I didn’t catch — In any case, I do things differently now and its both easier to work with and more reliable.
Rob
Hi Robin – I have a trouble-shooting question for you please. I have wired up two servos to throw my turnouts. I have everything installed and wired up and it doesn’t work. I can hear the servos clicking but they don’t move. I’ve checked the supply voltage and it’s ok. 6 volts supply and 5.4 volts when the servos are powered. I know my Arduino sketch works and the servos work since I tested them on the bench and everything works fine there. Do you have any suggestions or thoughts on trouble shooting? I’m using TowerPro SG92R servos. Thanks, Dave
The first question is what are you connecting your servos to — a pin on the Arduino board (which one?), or a PCA9685 board?
6 volts supply is actually high for most micro servos — but I don’t know the SG92R. 5 volts is where you want to be with SG90’s. Your Arduino pins are limited to 5v, a voltage mis-match could cause unpredictable behavior.
If everything worked on the bench, then the problem has to be in the wiring or the power feed on the layout. Perhaps something is reversed?
With more info, I might be able to help further.
R
I took your 3 turnout example up to 6, the limit of the analog pins on an Uno.I was looking to add shift in registers to add additional buttons, then add the Adafruit PWM servo control, but for the life of me I cannot figure out how to get the turnout’s button push if there is no pin to read. It’s been a long time since I worked with C or C++, but now that I’m retired, I want to get the planning and building up and running. Is there a way of using the shift In registers (I have CD4021), or should I accept the 6 or seven limit and use Nano’s spread out?
If I’m understanding correctly, you are running out of pins for buttons. Yes, the CD4021 is a good solution in that it only needs 3 pins on your Nano and gives you 8 ports, and you can chain additional chips. In order to use it you have to shift-in the data from the chip. The chip will give you an 8 bit value, where each bit corresponds to a port and is 1 if electrically active (eg, button was pushed), otherwise 0. If you have multiple chips chained, then you will get an 8 bit value for each.
You may have to write your own shift-in function because the Arduino shift-in function may not work with the CD4021. That was the case several years ago. Examples can be found on the web.
Here’s a device available on my commerce site that packages CD4021’s with additional components and a software driver to make setting up button sets easy: https://beaglebay.com/duinogear/product/dnin8-digital-input-duinonode/
R
Thank you. I got it to work, basically using modifying the Arduino CD4021 example and some bit twiddling, and changing up the TURN_DATA structure to remove the Analog pins. So now I have plenty of input, and plenty of control for the LEDs. Integrating Adafruit PWM Servo drivers to take those of the Arduino is the next step. So far I have discerned from your code that I need to remove the
#include // include the Servo library and add
#include
#include
instantiating I believe would look something like this – Adafruit_PWMServoDriver pwm[2] = {Adafruit_PWMServoDriver(0x40),Adafruit_PWMServoDriver(0x41)};
I added a couple of #define NUMBER_OF_SHIFT_REGISTERS_IN 1
#define NUMBER_OF_ADAFRUIT 2
changes to the TUNOUT_DEF are – uint8_t adafruit_num;
// This represents the Adafruit PWM driver associated with each servo – 16 servos per Adafruit attached to control turnouts.
uint8_t servo_pin;
// Adafruit pin for the servo associated with this turnout only 0-15 allowed per PCA 9685 that is why the adafruit_num is in the structure
uint8_t shift_in;
// CD4021 turnout/servo is associated with only , two needed for each adafruit
and of course the addition Latch, clock and data pins.
So when I go to do the initialization this code results in nada.
// initialize each turnout
for(int i = 0; i < NUMBER_OF_TURNOUTS; i++){
// attach the servo
// servos[i].attach(turnouts[i].data.servo_pin);
// set the pin mode for the button pin
// pinMode(turnouts[i].data.button_pin, INPUT);
// test and position the turnout by moving
// to divergent then to main positions
pwm[turnouts[i].data.adafruit_num].setPWM(turnouts[i].data.servo_pin, turnouts[i].data.pos_main, turnouts[i].data.pos_div);
//servos[i].write(turnouts[i].data.pos_div);
turnouts[i].pos_now = turnouts[i].data.pos_div;
setTurnout(i, ALIGN_MAIN);
}
You’re on the right track. You definitely don’t want to mix both the Adafruit and Arduino servo libraries at the same time – they are very different in the way they work.
R.