I'm using a I2C PCA9685 board connected to an Arduino nano, to control the position of turnouts on my model railroad. Whenever I want to change a turnout from it's straight to it's turn off position, I press a pushbutton and the turnout slowly moves to the desired position. So everything operates perfectly the way I wanted.
However, I'm not sure whether the servo is still powered when it is in the desired position. The power supply is becoming hot, even when no servos are moving.
Is there a way to switch off power to the servos when they are in whatever position and what is the effect of switching their power off to their current position?
Unless you specifically cut the supply off a servo will always be powered. Doing so keeps the servo in its commanded position. If you remove power from the servo then its output arm is free to move if a force is applied to it, which is usually not desirable
What may be happening with your servos is that they are, in fact, trying to move too far and are, therefore, continuing to draw power as they try to move against the end stop, thus stalling the motor. It would be worth checking that they are only moving as far as required and no more
Hi UKHeliBob, thanks for your remarks.
That is exactly what I do. The turnout movable rail is just touching the non-movable one.
I control the settings of the individual servo arms from their mid-position by adapting small changes to their mid-position value. For instance: if the mid-position has a PWM value of 300, I may change it to 310 and check the effect on the turnout movable rail position. And so on, until is just touches the non-movable rail. So, the servo motor should not be stalling.
Also, consequently there should be no force applied to the servo arm when its power is switched off.
My question remains: how can I switch off (and on!) the power from my servo?
To switch the power on and off at the required time you would need to use a relay or MOSFET
Your sketch would turn on the power by setting the state of a pin, command the servo to the required position, wait a short time to allow it to move and then power it off by changing the state of the pin.
Ideally the waiting period, although short, should be done using non blocking code rather than delay() and each servo would require a pin to control its power
What servos are you using?
What power-supply are you using?
if the servohorn is really only superslightly touching the fixed turnout the current of the servo should drop down to a low value.
Without knowing the datasheet of your servos and powersupply it is impossible to say in what range this current will be.
Do you have a digital multimeter that is able to measure currents as large as minimum 2 ampere?
If yes you shoud do current measurings
- servo with no load at all.
- servo connected to your mechanic but moving from a inbetween position to a still inbeteween position. Where inbetween position means not touch any endposition
- servo moving from one endposition to the other
If your numbers mean 300 and 310 microseconds
you are driving your servos out of specs.
Most servos can only deal with pulswidth between 700 and 2100 microseconds
a part of the servos can deal with 500 to 2500 microseconds
but I would be very astonished if a servo can deal with a pulse short as 300 microseconds
It might be that your servo is drawing a big current all the time because you are operating it out of specs
You've got servos that are in stall, because they're pushing hard against something immovable(like a stock rail).
Options:
- Tune the servo positions so that the servo position is such that the point rail lightly contacts the stock rail. A good indicator is if you can gently pull a slip of paper from between the two when the servo is supposed to be at it's commanded position. This tuning is excessively tedious if you need to do multiple units, due to editing/re-uploading code. I've put my positions in EEPROM, such that I can edit them 'in the wild'.
- shut off power to the servos when not in motion. As @UKHeliBob stated, this may result in servos that don't hold position well enough to prevent derailments, but you'll just have to test it out. I would suggest using a relay to switch the power to all your servos at the same time, or do one or two individual test cases.
What Servo library? If it's Servo.h, the code contains limit checks for the pulse widths; I don't recall the values, but they're within safe limits for most servos regardless of whether you use the pulse width commands, or degree commands.
As long as the servo gets position pulses, it will try to hold that position and it may switch the motor on to achive that. Nearly all servos don't actively control the motor if they dont get position pulses anymore. So the easiest way is to switch of the position pulses.
With model railway turnouts, the gear inhibition is sufficient to hold the position.
They may 'twitch' if you power them on again.
That is done, in Servo.h, by detaching the pin. That causes the pulse to not be sent.
The MoToServo class of the MobaTools library can be configured to do that by itself if the servo has reached its target position and the position does not change for a while. But I think @jfh uses a PCA9685 to create the pulses.
Good point - search the PCA library for a shutdown option, @jfh.
Stefan, and all others,
thanks for your remarks/suggestions.
As I'm a native Dutch speaker, I may make language mistakes in my answers to this forum. So whenever something is not clear to you, please don't hesitate to ask for more/better clarifications and/or detailed info.
I use MG90S micro servos, and the Adafruit_PWMServoDriver library to control them.
The PMW values are not microseconds, and are certainly not out of range; the servo arms do change to the defined positions and bring the movable rail to barely touching the stockrail.
So a given turnout may have a value of 310 for its normal position, and 280 for its reversed position. For example:
const int T1normal = 310;
const int T1reversed = 280;
T1 will then be set to its normal position with
pwm0.setPWM (T1, 0, T1normal);
I hope this example will clarify how it works. If anyone is interested, I have included the complete sketch code. Although all comment and variable names are in Dutch might nevertheless be understandable.
/***************************************************
Versie:22-04-2024 17:00
Sketch Siedwende_20240422.ino
Deze sketch is bedoeld om de servos en
leds van de modelspoorweg "Middelspoor",
deel station "Siedwende" aan te sturen
****************************************************/
#include <Wire.h>
#include <Bounce2.h>
#include <Adafruit_MCP23X17.h>
#include <Adafruit_MCP23X08.h>
#include <Adafruit_MCP23XXX.h>
#include <Adafruit_PWMServoDriver.h>
/*****************************/
// Aanvang Globale declaraties
/*****************************/
/*********************/
// Aanvang Datatype Spoor
/*********************/
// SpoorkeuzeKnop gekoppeld aan pinnumer op Arduino Nano
// Eerste is pin2;maximaal pin13 op nano
const byte SK110 = 2;
const byte SK111 = 3;
const byte SK120 = 4;
const byte SK121 = 5;
const byte SK130 = 6;
const byte SK131 = 7;
const byte SK132 = 8;
const byte SK133 = 9;
const byte SK134 = 10;
const byte SK310 = 11;
const byte SK311 = 12;
// SpoorkeuzeKnoppen KNA
const byte KNA = 11;// Knoppen Aantal
const byte Knoppen[KNA] = {SK110, SK111, SK120, SK121, SK130, SK131, SK132, SK133, SK134, SK310, SK311};
// SpoorLicht koppeling aan Pinnumer op MCP23X17#2 (mcp1)
const byte SL100 = 0;// pin#21-GPA0
const byte SL110 = 1;// pin#22-GPA1
const byte SL120 = 2;// pin#23-GPA2
const byte SL130 = 3;// pin#24-GPA3
const byte SL131 = 4;// pin#25-GPA4
const byte SL133 = 5;// pin#26-GPA5
const byte SL134 = 6;// pin#27-GPA6
const byte SL200 = 7;// pin#28-GPA7
const byte SL300 = 8;// pin#1-GPB0
const byte SL311 = 9;// pin#2-GPB1
/********************/
// Datatype SpoorLicht
/********************/
const byte SLA = 10;// SpoorLicht Aantal
const byte SL[SLA] = {SL100, SL110, SL120, SL130, SL131, SL133, SL134, SL200, SL300, SL311};
/*********************/
// Aanvang Datatype Wissel
/*********************/
// koppeling WisselServo op Adafruit_PWMServoDriver #1 (pwm0)
// maximaal 16 aansluitingen per pmw
const byte WS110 = 0;
const byte WS111 = 1;
const byte WS120 = 2;
const byte WS121 = 3;
const byte WS132 = 4;
const byte WS134 = 5;
const byte WS310 = 6;
const byte KR131 = 7;
// Waarden voor Recht en Afbuigend per WisselServo
const int RWS110 = 130;
const int AWS110 = 280;
const int RWS111 = 295;//v 295
const int AWS111 = 270;//v 270
const int RWS120 = 130;
const int AWS120 = 280;
const int RWS121 = 220;//
const int AWS121 = 300;//
const int RWS132 = 260;//v 260
const int AWS132 = 305;//v 305
const int RWS134 = 290;//v 300
const int AWS134 = 325;//v 350
const int RWS310 = 270;//v 270
const int AWS310 = 335;//v 335
const int RKR131 = 280;
const int AKR131 = 320;
struct WS// Datatype WisSel
{
const byte WSpn;// Wissel pinnummer
const char* WSns;// Wissel naamstring
const int WSre;// Ingestelde waarde voor rechtdoor
const int WSaf;// Ingestelde waarde voor afbuigend
};
const byte WSA = 8;// WisSel Aantal
WS Wissel[WSA] =
{
{WS110, "WS110", RWS110, AWS110},
{WS111, "WS111", RWS111, AWS111},
{WS120, "WS120", RWS120, AWS120},
{WS121, "WS121", RWS121, AWS121},
{WS132, "WS132", RWS132, AWS132},
{WS134, "WS134", RWS134, AWS134},
{WS310, "WS310", RWS310, AWS310},
{KR131, "KR131", RKR131, AKR131},
};
// Wissellicht
// maximaal 16 op MCP23X17 #1 (mcp0)
const byte WL110 = 0;// pin#21-GPA0
const byte WL111 = 1;// pin#22-GPA1
const byte WL120 = 2;// pin#23-GPA2
const byte WL121 = 3;// pin#24-GPA3
const byte WL130 = 4;// pin#25-GPA4
const byte WL131 = 5;// pin#26-GPA5
const byte WL132 = 6;// pin#27-GPA6
const byte WL133 = 7;// pin#28-GPA7
const byte WL134 = 8;// pin#1-GPB0
const byte WL135 = 9;// pin#2-GPB1
const byte WL310 = 10;// pin#3-GPB2
const byte WL311 = 11;// pin#4-GPB3
// Datatype WisselLicht
const byte WLA = 12;// Aantal WisselLichten
const byte WL[WLA] = {WL110, WL111, WL120, WL121, WL130, WL131, WL132, WL133, WL134, WL135, WL310, WL311};
// Aanvang Globale Variabelen
byte i = 0;
byte LA = LOW;
byte LU = HIGH;
// Systeem Variabelen
Adafruit_PWMServoDriver pwm0 = Adafruit_PWMServoDriver(0x40);
Adafruit_MCP23X17 mcp0;
Adafruit_MCP23X17 mcp1;
Bounce*Knop = new Bounce[KNA];
// Einde Systeem Variabelen
/*********************/
// Einde Globale Variabelen
/*********************/
// Einde Globale declaraties
/*********************/
/*********************/
//Aanvang Functies
/*********************/
// Initialisaties
void setup()
{
// Initialiseer Adafruit_PWMServoDriver board #1
// Adafruit_PWMServoDriver(0x40)
pwm0.begin();
pwm0.setPWMFreq(50);
// Initialiseer Adafruit_MCP23X17 I/O Expanders;
// max aantal MCP23X17's is 8
byte MaxLedMCP = 16;// maximum aantal led's per MCP
// Initialiseer Adafruit_MCP23X17 mcp0;
mcp0.begin_I2C(0x20);
for (byte i = 0; i < MaxLedMCP; i++)
{ mcp0.pinMode(i, OUTPUT);// alle I/O pins output
}
for (byte i = 0; i < WLA; i++)
{ mcp0.digitalWrite(WL[i], LA);
}
//Initialiseer Adafruit_MCP23X17 mcp1;
mcp1.begin_I2C(0x21);
for (byte i = 0; i < MaxLedMCP; i++)
{ mcp1.pinMode(i, OUTPUT);// alle I/O pins output
}
for (byte i = 0; i < SLA; i++)
{ mcp1.digitalWrite(SL[i], LA);
}
// Initialiseer Knoppen
for (byte i = 0; i < KNA; i++)
{ Knop[i].attach(Knoppen[i], INPUT_PULLUP);
//Knop[i].byteerval(50);
digitalWrite (Knoppen[i], HIGH);
}
// Initialiseer alle Wissels op rechtdoor
for (byte i = 0; i < WSA; i++)
{ pwm0.setPWM (Wissel[i].WSpn, 0, Wissel[i].WSre);
}
// Zet ter controle eenmalig alle WisselLichten en Spoorlichten Aan
AlleLichten(LA);
}
// Einde Initialisaties
// Schakel alle WisselLichten en SpoorLichten Aan of Uit
void AlleLichten(byte Stand)
{ for (byte i = 0; i < WLA; i++)
{ if (Stand == LU)
mcp0.digitalWrite(WL[i], LU);
else
mcp0.digitalWrite(WL[i], LA);
//delay(10);
}
for (byte i = 0; i < SLA; i++)
{ if (Stand == LU)
mcp1.digitalWrite(SL[i], LU);
else
mcp1.digitalWrite(SL[i], LA);
}
}
// Zet op basis van gekozen SpoorKnop alle nodige:
// Wissels Afbuigend of Rechtdoor,
// WisselLichten Aan of Uit
// SpoorLichten Aan of Uit
void ZetWissel(byte Spoorknop)
{
AlleLichten(LU);// eerst alle Spoor- en Wissellichten Uit
switch (Spoorknop)
{
case SK110://Spoor 110
{
pwm0.setPWM (WS110, 0, (Wissel[WS110].WSre));
//
mcp0.digitalWrite(WL110, LA);
//
mcp1.digitalWrite(SL110, LA);
mcp1.digitalWrite(SL200, LA);
}
break;
case SK111://Spoor 111
{
pwm0.setPWM (WS111, 0, (Wissel[WS111].WSre));
//
mcp0.digitalWrite(WL111, LA);
//
mcp1.digitalWrite(SL100, LA);
mcp1.digitalWrite(SL110, LA);
}
break;
case SK120://Spoor 120
{
pwm0.setPWM (WS110, 0, (Wissel[WS110].WSaf));
pwm0.setPWM (WS120, 0, (Wissel[WS120].WSaf));
//
mcp0.digitalWrite(WL120, LA);
//
mcp1.digitalWrite(SL120, LA);
mcp1.digitalWrite(SL200, LA);
}
break;
case SK121://Spoor 121
{
pwm0.setPWM (WS111, 0, (Wissel[WS111].WSaf));
pwm0.setPWM (WS121, 0, (Wissel[WS121].WSaf));
//
mcp0.digitalWrite(WL121, LA);
//
mcp1.digitalWrite(SL100, LA);
mcp1.digitalWrite(SL120, LA);
}
break;
case SK130://Spoor 130
{
pwm0.setPWM (WS110, 0, (Wissel[WS110].WSaf));
pwm0.setPWM (WS120, 0, (Wissel[WS120].WSre));
pwm0.setPWM (KR131, 0, (Wissel[KR131].WSaf));
//
mcp0.digitalWrite(WL130, LA);
//
mcp1.digitalWrite(SL130, LA);
mcp1.digitalWrite(SL200, LA);
}
break;
case SK131://Spoor 131
{
pwm0.setPWM (WS111, 0, (Wissel[WS111].WSaf));
pwm0.setPWM (WS121, 0, (Wissel[WS121].WSre));
pwm0.setPWM (KR131, 0, (Wissel[KR131].WSre));
//
mcp0.digitalWrite(WL131, LA);
//
mcp1.digitalWrite(SL100, LA);
mcp1.digitalWrite(SL131, LA);
}
break;
case SK132://Spoor 132
{
pwm0.setPWM (WS111, 0, (Wissel[WS111].WSaf));
pwm0.setPWM (WS121, 0, (Wissel[WS121].WSre));
pwm0.setPWM (WS132, 0, (Wissel[WS132].WSre));
pwm0.setPWM (WS134, 0, (Wissel[WS134].WSaf));
pwm0.setPWM (KR131, 0, (Wissel[KR131].WSre));
//
mcp0.digitalWrite(WL131, LA);
mcp0.digitalWrite(WL132, LA);
mcp0.digitalWrite(WL134, LA);
//
mcp1.digitalWrite(SL100, LA);
mcp1.digitalWrite(SL131, LA);
mcp1.digitalWrite(SL134, LA);
}
break;
case SK133://Spoor 133
{
pwm0.setPWM (WS111, 0, (Wissel[WS111].WSaf));
pwm0.setPWM (WS121, 0, (Wissel[WS121].WSre));
pwm0.setPWM (WS132, 0, (Wissel[WS132].WSaf));
pwm0.setPWM (WS134, 0, (Wissel[WS134].WSre));
pwm0.setPWM (KR131, 0, (Wissel[KR131].WSre));
//
mcp0.digitalWrite(WL131, LA);
mcp0.digitalWrite(WL132, LA);
mcp0.digitalWrite(WL133, LA);
mcp0.digitalWrite(WL134, LA);
mcp0.digitalWrite(WL135, LA);
//
mcp1.digitalWrite(SL100, LA);
mcp1.digitalWrite(SL131, LA);
mcp1.digitalWrite(SL133, LA);
}
break;
case SK134://Spoor 134
{
pwm0.setPWM (WS110, 0, (Wissel[WS110].WSaf));// alleen in deze test, als ok, dan verwijderen
pwm0.setPWM (WS132, 0, (Wissel[WS132].WSre));
pwm0.setPWM (WS134, 0, (Wissel[WS134].WSaf));
pwm0.setPWM (WS310, 0, (Wissel[WS310].WSre));
//
mcp0.digitalWrite(WL131, LA);
mcp0.digitalWrite(WL132, LA);
mcp0.digitalWrite(WL133, LA);
mcp0.digitalWrite(WL134, LA);
mcp0.digitalWrite(WL135, LA);
mcp0.digitalWrite(WL311, LA);
//
mcp1.digitalWrite(SL100, LA);
mcp1.digitalWrite(SL131, LA);
mcp1.digitalWrite(SL133, LA);
mcp1.digitalWrite(SL134, LA);
mcp1.digitalWrite(SL300, LA);
mcp1.digitalWrite(SL311, LA);
}
break;
case SK310://Spoor 310
{
pwm0.setPWM (WS110, 0, (Wissel[WS110].WSaf));
pwm0.setPWM (WS120, 0, (Wissel[WS120].WSre));
pwm0.setPWM (WS121, 0, (Wissel[WS121].WSaf));
pwm0.setPWM (WS134, 0, (Wissel[WS134].WSre));
pwm0.setPWM (WS310, 0, (Wissel[WS310].WSaf));
pwm0.setPWM (KR131, 0, (Wissel[KR131].WSaf));
//
mcp0.digitalWrite(WL130, LA);
mcp0.digitalWrite(WL310, LA);
//
// mcp1.digitalWrite(SL130, LA);
mcp1.digitalWrite(SL200, LA);
mcp1.digitalWrite(SL300, LA);
}
break;
case SK311://Spoor 311
{
pwm0.setPWM (WS134, 0, (Wissel[WS134].WSre));
pwm0.setPWM (WS310, 0, (Wissel[WS310].WSre));
//
mcp0.digitalWrite(WL134, LA);
mcp0.digitalWrite(WL135, LA);
mcp0.digitalWrite(WL311, LA);
//
mcp1.digitalWrite(SL134, LA);
mcp1.digitalWrite(SL300, LA);
mcp1.digitalWrite(SL311, LA);
}
break;
}
}
/*********************/
// Einde Functies
/*********************/
/*********************/
// Begin Hoofdprogramma
/*********************/
void loop()
{
for (byte i = 0; i < KNA; i++)
{
Knop[i].update();// Lees alle SpoorKnoppen
// Knop[i].interval(10);
if (Knop[i].fell())
{
byte Spoorknop = Knoppen[i];
ZetWissel(Spoorknop);// Zet Wissels en WisselLichten
}
}
}
/*********************/
// Einde Hoofdprogramma
/*********************/
/*********************/
// Einde Sketch Siedwende
/*********************/
Given a 50 Hz update rate, aka 20 ms, it's 310/4096 * 20 ms for pulse width, or 1.514 ms for the normal position, vs 1.367 ms for the reversed position.
Got it. Okay. Doesn't really matter how you're arriving at the result, that's what's under the hood.
I don't see the concept of an on/off in the servo features of that library, so you may not have much option. It sounds to me like you have a handle on the stall current possibility, so what remains is just quiescent current for the servos, though I'm surprised it's that high; you should check the specifications from the supplier.
Camsysca,
thanks for your explanation how to calculate the turnout values from pulse width into ms.
In the next days I will try to measure the actual currents for several servos when moving as well as in the normal and reverse position.
Of course I will inform you.
One solution that might get you closer to what you want could be
if(position != lastPosition) {
Servo.write(position);
lastPosition = position;
}
Might help with some of the heat. Note that I've never tried this myself.
This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.