randy
PM me if your interested in discussing this. i'd really like to see you post code that is more readable and reusable by others.
BTW, object oriented coding can be done in many languages, including assembly. C is no exception.
greg
greg - Philadelphia & Reading / Reading
BTW, i DO understand the concept of creating an array of STRUCT for the various variables, then each Metroo call would be like chkbutton(1) or chkbutton(2), or moveservo(1), moveservo(2), etc. and yes, if I expanded to 12 servers, I would just have to change my array declaration to 12 and bingo, now it runs 12 (well, there's have to be some setting of the pin definitions in there - for the inputs and the outputs). Is that more proper by OOP standards? Probably. Is it easier for someone who doesn't know these things to figure out? Probably not. Famous last workds "It will never grow beyond this" but - it won't. The smaller chips are cheaper, because they are used in dime a dozen Arduinos, and the bigger ones with more I/O either aren't fully capable in a PDIP package, or just plain aren't available. So in this case it's true, i will never design one that could use the same code but drive 6 servos, or something like that. At this point, I am almost willing to say I will NEVER use SMD components (except as resistors for resistor wheelsets - and possibly LEDs for loco lighting) because I simply can not see well enough to work with the tiny stuff, magnifiers or not. Wider pitch things, I probably could drag colder. But the finer stuff? I'm not going to invest in a board assembly microsofpe, or a reflow oven. ANd BGA parts - forget about it, that is NOT a DIY thing. Those "Fix your video card" YouTube videos - yeah, they are kidding themselves if they thing they are truly reflowing all the balls under that GPU. Plus - I'd rather not run long runs of servo cable everywhere, and 2 is a nice number because it works for a crossover. I went through this over and over - the idea that if I eliminated the remote control (which isn't needed for a ayrd) I could add a third servo - but I fall a couple of pins short, even with the single relay method. So si settled on a standard board that will drive 2 servos, asosciated LEDs and switches, and have a remote control option. In fact I MIGHT use the remote input to drive the yard ones using an input matrix for route controol, instead of 2 buttons for every turnout. But I figured if I designed all kinds of different boards for every special conditioon on the layout, I'd spend all my time designing hardware and not building a layout. So - one standard board, the same everywhere, no mucking with special conditions everywhere.
--Randy
Modeling the Reading Railroad in the 1950's
Visit my web site at www.readingeastpenn.com for construction updates, DCC Info, and more.
It's just that one specific thing I don't understand where you are headed. First you say it will elminate the need for an "is it moving" flag but then it does.
Then you suggest changing where I make the Metro function calls. They HAVE to be in the loop, and reducing the whole thing to just one metro block would defeat the purpose of allowing servo 2 to be triggered while servo 1 is in motion.
I can theoretically remove one test and assume the direction is always correct, and just test if the servo is at min or at max instead of having that test then follow it up with a test for which way it was going, which is what tells em what lights to turn on and what relays to set. That may be a safe assumption to make, since if it gets to the min value, it MUST have been going to the Normal positon, and if if it gets to the max, it must have been going to the Reverse position. By combining that logic, I can eliminate one if block. But I can't elminate the direction flag, unless I break the move routines into two complete sets, one for each direction, and call the MoveToNormal if the normal button is pressed and MoveTo Reverse is the reverse button is pressed. Then perhaps I can eliminate the multiplication in the math and the normal side would just subtract the step and the reverse side would jsut add the step. OK, but that's still not a whole lot simpler, as the servo move stuff needs to be within its own Metro block to allow asynchronous operation of the program.
The strict requirement for object dependency is exactly why my control program will NOT be written in vb.net. It's next to impossible to do what is EXTREMELY simple (and completely understandable) with pre .Net versions - and the example used in many VB CMRI programs. That being that an array of controls on a form is exactly that, an array - and can be referenced by an array subscript as well as the name. To use the enforced pure OOP in VB.Net - yes, I was able to make a sample CTC panel but it required having code in each object. OK, easy enough to do I suppose by making a custom class and then using that throughout the form. But the 'old' not strictly OOP method of actually allowing the encapsulating object to have references to the encapsulated objects is FAR easier to understand. It's not ALWAYS better, these new ways. I'd still rather write a program that needed to read data in Foxpro as opposed to VB.Net simple because I don't need that absolutely unecessary intermediate XML layer. But that ship has sailed and now we are stuck.
I'm not saying my code is perfect by any means. I'm sure as I tweak it I will find other things to improve - I am pretty sure across the whole thing there are redundancies I can remove - but I also am taking the safe approach and initializing every variable even if the first thing that happes is it gets set to some stored value. Rember that these are completely independent programs, this program will in no way interact with servo controller #2 controlling the turnouts at the next crossover down the track. This is a single purpose device - move the srvos. Relays optional - heck I may remove them entirely. Certainly the "power' relay, because I will never use any of those old Shonohara turnouts that might need that feature. But - there is nothing more modular needed here - if I am more careful with my breadboard space utilization I can add a second one of these, load the same code into another Nano, and have 4 working servos. But on an individual basis - I have no intent of making a more massive version that drives a half dozen servos. There just aren;t enough pins. A multi-node system is just that - multiple nodes of the same thing, there is no conflict with variables or anything of the sort, they are connected only via the I/O pins. The comm nodes with the RS484 ports are also going to be simple - no real logic on them at all other than decoding a couple of input pins to set the address. Otherwise it's more or less the same code put out by others that reads the bus and looks for input or output messages addressed to it, and transfers the data and unpacks it into bits to send to the port expander chips. But that's another thread.
Again - is it perfect? No. Does it work? Aftr running it for a few hours at the Modeler's Meet, yes, it does work just fine as-is. Am I done with the code and ready to call it complete? No. There are things I do want to change up. And some features I want to see if I can add.
I get that people are following this. What they need to understand is that this is a special case of a fairly fancy bit of kit, not unlike the commercial products,a dn that the make servos go back and forth with an Arduino does NOT take 100's of line sof code. It takes a couple dozen tops, and most of that is just setting up the pins. The extra code in mine is to make is go slow, light up LEDs, and have additional controls beisdes the manual pushbuttons. And store the last position. That's at least half the code right there. It currently comes out to 605 lines, counting the whitespace and comments. It compiles to 5.5k, considing the most basic Blink 1 LED sketch uses almost 1K, that's not too bad. Absolutely optimized? Clearly noot, but I never claimed it was. I will make some changes, but at some point there is a diminishing return. I have no intention of spending weeks on the code design, expecially since it actually works now. I will clean it up where I can, and add more comments - then I will consider it good to go.
BMMECNYCThis is why Im not really interested in Arduino. I have no desire to write or debug 100s of lines of code,
Randy
the modelers on the forum respect your comments. I think they will look at your code as an example of how to write Arduino code. It would be good if it was a high quality example.
While I've written/debugged lots of embedded code over my career, I've also spent a lot of time reviewing, debugging, fixing and rewriting code written by others that was insufficiently tested, took the simple approach, ignored cases or wasn't maintainable.
I'm sorry i'm not clear enough for you to understand my comments for modularity, isolating functionality and minimizing inter-dependency between different parts of the code (OOD), all of which make code more readable and reusable by others. this also helps w/ multi-node systems.
Not sure I can be any clearer without showing code (try PM). In Elements of Programming Style Kernighan describes how to program by exposing bugs and showing how to improve code in examples from textbooks on programming and journal articles (they didn't make up bad examples). They show what makes code bad and how to make it better.
If I was making a device to control a half dozen or more servos, I probably would make it a function with the appropriate structures. Having come from a strictly proceduarl environment (BASIC and FORTRAN, and direct machine labguage programming are where I started), I have a tough time with the whole "it makes it easier" thing because to me it does not. I much more complex routine and a structure definition, as opposed to taking code I know works and doing a quick copy/paste (and yes, slight edit - change all the 1's to 2's - not exactly rocket science). Faster? I doubt it is much faster in execution, but it surely is faster to write it my way. If I needed more than 2 - yeah, then it starts getting painful to copy/paste/edit AND it could cause issues needing too many time routines. So it would need a different approach. Never said this was totally scalable. Half of it might go away - after seeing how the new Peco Code 70 turnouts are being done, and the mention that this design would trickle over to the Code 83 line as well, I might not need ANY of that relay stuff.
Using a #DEFINE absolutely saves memory in the Atmel enviroonment, variables are stored in the precioous RAM, substituting a symbol using #DEFINE doesn't shorten the code but it never places that value in the RAM stack. The way I have defined non-const integers does use memory. The actual variable itself that gets set to some #DEFINEd symbol still uses the same amount of memory. servo1dir = -1 uses the same memory as servo1dir = NORMAL. But I'm not even remotely worried aboout it, I'm still using only a small fraction of it, and this is probably the biggest program for the whole system, I don't think the CMRI node will need as much code. Given that some of the smaller Atmega micros actually cost MORE, there's no cost factor driving to make the code absolutely as tiny as possible - and there are likely C tricks I am not familiar with that could make it even smaller, at the expense of readability. I've seen some fo them discussed. I also remember the "one liner" BASIC contests they would run in 80 Micro magazine - some pretty amazing proograms in just one line but unless you were expert+ level in BASIC you would NEVER be able to figure out what it did. Efficient for some values of efficient.
I'm still not sure what you mean, that's EXACTLY what it is doing right now. It makes one Metro call in the loop() and then moves the servo if the move flag is set or does nothing if it is not. The only difference is, I have 2 of them, one for each servo, rather than making a function out of it and passing which servo I want to test. EEPROM is ONLY being updated if a servo moves. I tested and perfected this with a LOT of serial.print and serial.println commands in each routeine, encapsualted in #ifdef debug /#endif blocks, when I turn debug on it actually does not write to the EEPROM but prints to the serial monitor when it would be doing so and what it would be writing. If the thing just sits there with no buttons pressed, it does nothing. Only when you push a button and the servo moves does it write anything to EEPROM. Anything else would quickly wear out the memory cells.
Awkward? How so? I already have variables defined that are needed by the servo move routing anyway (I guess they don't have to be variables, but then I'd have to go through the entire code to change the endpoints or the stepping rate) and they are global variables anyway. All it does is set servo1pos or servo2pos to somehing other than the endpoint to get it past the move loop. I'd have to firther break the test up into independent IF statements to get away from that, actually making it longer - that way the test would fall through as long as the direction was changed and I could remove the code from the button detection. perhaps that would be more obvious. Actually - the button code does NOT need to know the endpointys, just the current position and the step size. The endpoint variables do not appear in my button code. I have direction and I have step size - the code in the button routin basically sets servo1move to true and sets servo1pos to servo1pos + (servo1dir * servo1step) (and yes I always use parentheses in math even if technically it's not needed. ANd I was never much of a fan of C's += and -+ and similar combined operators and half the time I completely forget about them and just write out the whole equation instead of shortcutting it.
rrinker Possibly. But there are only 2 servos, so I thought it just as easy to simply copy and paste the code and make individual routines.
isn't it more like copy-paste-edit. don't you need to change all the servo1... variable names to servo2.
an array of statically initialized structures can be used to maintain the state of each servo and a pointer to the structure passed to a common servo routine. Much less code and you don't need to make the same change in multiple places in the code.
rrinker Probably. Most verything else is a CONST or #DEFINE. I could then leave out the comments further up in the code where they are declared which says which is which. As it is, I'm using maybe 8% of the variable space in the rather small RAM of the mega328.
do you think replacing a constant in the code with a symbol, enum/#define saves memory?
rrinker servo1move gets set to false when the servo reaches an endpoint. It gets set to true when a button is pressed for the direcion opposite the current position, or a remote input changes to the opposite position. It's needed because the Metro code is within the Arduino loop() and this gets called ever 50ms regardless. If everything is stationary, thent here is no reason to change those variables or update the EEPROM. Withotu tracking this and skipping over the endpoint check when the servo is not moving, it was writing an update to the EEPROM ever 50ms. Not good.
yes, you need servo1move because of the of the way the code is written
wouldn't it be simpler, more maintainable and less code, to have one call to the Metro code in the Arduino loop() and code that determines when to run the servo code for each servo based on a flag (i.e. servoMove == { CW, CCW}) set by button code and cleared when the servo is in position? similar to what you are doing but it is tested outside the servo code so that it doesn't need to be repeatedly called. ...
couldn't the EEPROM be updated outside the servo code only if any of the servos moved or anythinhg else affecting EEPROM contents has changed?
rrinker Again, this is only a small part of the code. The pressing of a button sets the servo position to the current position + one increment or - one increment, so the first call to this code after a button is pressed causes it to fall past the "check if at end" and do just the simple move the servo
Wow! that seems awkward. probably a -2 (Do not submit) git review
Now the button test code needs to know the endpoint positions of each servo. That implies that the button test and servo code need to be running on the the same Arduino unless you want to share servo positions between Arduino nodes.
wouldn't it be easier for the button code to simply change the servo state (i.e. servoMove) and let the servo code be the only code that affects servo the position variable?
BTW you're not THAT far, come out to the Reading Modelers Meet and I can show you the whole thing, and some of my other stuff. You cna register at the door, plus there is a low cost spectator admission which means you can come in and see everything, just not participate in the clinics or layout tours. But in addition to the meet stuff you also get access to the RCT&HS museum
Took a copy and paste. I know the problem - the provider that hosts this site for Kalmbach has the site behind a proxy (and probably load balancer as well), which is how it should be. But, they have the connection timeout set too low for someone who types slow, needs to go look stuff up while posting, or types long messages.
OK, let's try this again
gregc Randy i realize your code is a work in progress. i ran your code in simulation (C on my laptop). but first some general comments: looks like this code using servo specific variable names (e.g. servo1pos) need to be duplicated for every servo. A generic routing specifying a servo by number and using arrays of structures (hor classes) to maintain state for each servo would improve maintainability.
i realize your code is a work in progress.
i ran your code in simulation (C on my laptop). but first some general comments:
Possibly. But there are only 2 servos, so I thought it just as easy to simply copy and paste the code and make individual routines.
gregc would be better to use enum or #define for direction (e.g. CW/CCW) instead of 1/-1
Probably. Most verything else is a CONST or #DEFINE. I could then leave out the comments further up in the code where they are declared which says which is which. As it is, I'm using maybe 8% of the variable space in the rather small RAM of the mega328.
gregc i don't see a need to update the servo position doing servodir * servostep since you have separate lines for each case. You can do servopos += or servopos -= servostep.
There's only one line for the plain move, where it needs to adjust the position by the step - it falls to that line if it has not reached the end of movement. The only place it's broken out is when it HAS reached the end, then it has to do differnet things depending on which extreme it has reached. The line in question is execued for both directions, same line of code. So it needs the direction factor.
gregc if setsavepos() and writepos() are EEPROM write, are you writing all values or just the changes for the one servo
Setsavepos() does a bitmask to change a single byte variable to store the position of both servos. 0x00 means both normal, 0x0F means 1 reverse, 2 normal, 0xF0 means 2 reverse, 1 normal, and 0xFF means both reverse. What gets written to the EEPROM is 2 bytes, one is a counter and one is the position byte. The counter loops from 0 to 255, and when it rolls over, the memory position is advanced by 2 to start the next block. Every pair of locations gets written to 256 times, and one pass through the memory pointer is 500 steps. The 328 has 1K EEPROM, starting at address 1010 I store version information and the memory location pointer, when gets initialized on the first run and then read to know that there is already valid data on subsequent runs. The memory pointer is loaded, then the counter and current position are loaded, the servos and LEDs set, and the loop starts. When you change a servo position, the counter is incremented. If it doesn;t roll over, then the current counter and current position bytes are written to the memory pointer address. If the couner rolls over, then the memory pointer is incrememented by 2 and gets written to the storage location it uses. The counter, now 0, and the position byte get written to the new pointer address and things continue.
gregc not sure how servo1move gets set. Its use implies this code is invoked repeatedly.
servo1move gets set to false when the servo reaches an endpoint. It gets set to true when a button is pressed for the direcion opposite the current position, or a remote input changes to the opposite position. It's needed because the Metro code is within the Arduino loop() and this gets called ever 50ms regardless. If everything is stationary, thent here is no reason to change those variables or update the EEPROM. Withotu tracking this and skipping over the endpoint check when the servo is not moving, it was writing an update to the EEPROM ever 50ms. Not good.
gregc i found that once the servo pos was set to an endpoint (e.g. min or max), the "if (servo1pos <= servo1min ... servomax <= ..." test prevented moving the servo in the opposite direction. It seems the test should be if (CW == servodir) { if (servo1Max > servo1pos) { Servo1.write (servo1pos += servostep); return 1; } } . . . return 0; if the code gets beyond (hence the return) the tests for CW and CCW, it can set LEDs, EEPROM and return 0 indicating done.
i found that once the servo pos was set to an endpoint (e.g. min or max), the "if (servo1pos <= servo1min ... servomax <= ..." test prevented moving the servo in the opposite direction. It seems the test should be
if (CW == servodir) { if (servo1Max > servo1pos) { Servo1.write (servo1pos += servostep); return 1; } } . . . return 0;
if the code gets beyond (hence the return) the tests for CW and CCW, it can set LEDs, EEPROM and return 0 indicating done.
Again, this is only a small part of the code. The pressing of a button sets the servo position to the current position + one increment or - one increment, so the first call to this code after a button is pressed causes it to fall past the "check if at end" and do just the simple move the servo.
gregc not clear if you call this or similar routines every cycle or just when a particular servo needs to move (i.e. why servo1move?).
not clear if you call this or similar routines every cycle or just when a particular servo needs to move (i.e. why servo1move?).
That's exactly why it's there, this code runs every 50ms. See above.
gregc i downloaded the Metro library. It's pretty thin. not sure of your need to manage multiple operations simultaneously on a single arduino. (I think the Metro code should be called outside the servo code to determine when the servo code should be invoked while checking other things). one approach is to repeatedly call the routine, when needed, with a specified direction either periodically/aperiodically. It returns 1 if not done and 0 when done.
i downloaded the Metro library. It's pretty thin. not sure of your need to manage multiple operations simultaneously on a single arduino. (I think the Metro code should be called outside the servo code to determine when the servo code should be invoked while checking other things).
one approach is to repeatedly call the routine, when needed, with a specified direction either periodically/aperiodically. It returns 1 if not done and 0 when done.
And once again a long and detailed reply gets eaten by the site... and of course Chrome updated today and while up until yestrday I could just refresh and it would warn me about reposting the same form data, today - nothing.
With less travel, you just need to use smaller steps, I don't know how you are doing the servo move, but I use the Metro library to do multiple timing dependent things all at the same time - check for button presses, move the servos, check for remote inputs. You create a Metro object for each item, setting the delay in milliseconds, and then in the main loop you just check each metro object with an if. Makes it easy peasey to do multiple things without all sorts of variables and checking against millis(). And you get automatic debounce of the buttons, since it only checks the state once per however many microseconds you configure it for. For my servos, right now I have it set to move a specified increment every 50 milliseconds. The the following code in the loop checks for the servo and either moves it or, if it is at the end position, sets the LEDs and other variables, and then writes the EEPROM.
if (servo1Metro.check() == 1) { // Servo 1 movement control // Move interval for Servo 1 if (servo1move) { // Servo is moving) if (servo1pos <= servo1min || servo1pos >= servo1max) { // if we reached the end of travel if (servo1dir == 1) { // reverse sw1nstate=LOW; sw1rstate=HIGH; sw1frstate = HIGH; sw1prstate = LOW; servo1pos = servo1max; servo1move = false; } else { // normal sw1nstate=HIGH; sw1rstate=LOW; sw1frstate = LOW; sw1prstate = LOW; servo1pos = servo1min; servo1move = false; } digitalWrite(SW1FR, sw1frstate); Servo1.write(servo1pos); digitalWrite(SW1NLED, sw1nstate); digitalWrite(SW1RLED, sw1rstate); digitalWrite(SW1PR, sw1prstate); setsavepos(); writepos(); } else { // Just move the servo, we are in the middle servo1pos = servo1pos + (servo1dir * servo1step); Servo1.write(servo1pos); } } }
servo1dir is 1 is moving towards reversed, -1 if moving towards normal. servo1step is the incrememnt per time step, currently 2. servo1min and servo1max are the endpoints, I'm using 20 and 140 in this demo just to get a long travel - the servos aren't attached to turnouts so it's easy to see them move. servo1move is true if the servo is in the middle of travel, or false if it is at the endpoints - so the functions don't keep triggering. sw1nstate and sw1rstate are high and low depending on the endpoint, and controlt he indicator LEDs. sw1prstate is the power control relay, and sw1frstate is the frog polarity relay.
I don't think I'd go much higher than making a move every 50ms, it will start to get jerky. Larger increments likewise make it more jerky. For a shorter throw, perhaps an increment of 1 every 60ms might be ok, so for a 53 degree swing it would take 3.18 seconds to move end to end. About the same as it take mine to go 20-140 in steps of 2 ever 50ms.
I was also worried that the time it takes to do EEPROM writes would mess with the motion, but I can start one moving, wait a bit, then start the second one moving, and when the first one finishes and updates the EEPROM, the second one doesn't miss a beat. ANd it's always writing 2 bytes to EEPROM, sometimes 4, when the counter rolls over - that's how I spread the writes across the entire memory space to get the most possible writes with no location getting over 100k writes, which is the datasheet maximum allowed (although others have done empirical testing and gotten closer to 1 million writes per cell before seeing any errors). No worries though, 100k writes is a LOT of position changed per day for more years than I expect to be needing these things to work, and they will never be operating every day, even I won't be playing with the trains 4 hours a day 7 days a week.
That's a lot like the one I put together just to test, the servo is stuck right to the bottom of the turnout like a Peco switch motor. Not sure if I want to go that way or not - since my layout will be new, I think I'd rather drill a hole than cut out a big opening to fit a Peco motor/servo from the top. Two layouts ago it was also foam, and I mounted my Tortoises from the top, but the last one, while also foam, I mounted the servos on the bottom and just drilled a hole. This time it will be plywood, so I'm leaning towards the bottom mount.
rrinker That's perhaps the one downside of Mel's one motor for 4 sets of points approach - it has to be all in pretty good adjustment to get them to all equally move. With a Tortoise, it makes sense, they aren't cheap. But considering 4 servos are still cheaper than one Tortoise, each could be individually powered without all the complex mechanical design.
That's perhaps the one downside of Mel's one motor for 4 sets of points approach - it has to be all in pretty good adjustment to get them to all equally move. With a Tortoise, it makes sense, they aren't cheap. But considering 4 servos are still cheaper than one Tortoise, each could be individually powered without all the complex mechanical design.
That's sort of the idea of figuring a mount that goes over center. Although I'm not sure even that is needed, actually. Before I hooked up all of the Tam Valley Singlet controllers on my old layout, I would just reach under and turn the servo by hand. Even with no power applied there was no issue keeping the points tight enough. You definitely don't need to go a full 0-180 to get to a point where the back pressure is pushing the servo further in the direction of travel instead of resisting it.
RR_MelTo maintain rail contact to insure the moving rail is close enough to prevent a wheel from climbing the point and derailing the servo needs to apply slight pressure against the rail.
wouldn't the servo be more likely to hold it position w/o being powered/pulsed if the endpoints are near their extremes?
any back pressure would NOT be tangential, causing the arm to rotate
even if 0-180 deg travel is not possible, anything near the extremes may be enough
Randy:
Great progress!
Dave
I'm just a dude with a bad back having a lot of fun with model trains, and finally building a layout!
I still think you are OK current-wise. It's hard to find specs on the 9G servos, but 14ma is nearly as low as a Tortoise, and the 4ma side is lower than any Tortoise will stall at. Given that the average operating current of the servo is somewhere around 400ma, with a peak of near 1A, 14ma is nothing.
RR_MelOne Tortoise operates all four sets of point rails on my Mel made double crossover.
You've posted that phrase before but this is the first time I've seen a picture.
I nominate you for MR Wizard of 2018.
Henry
COB Potomac & Northern
Shenandoah Valley
If the current is much higher in one direction, you probably can back off a little ont he high side and still have it push hard enough. It might be catching on the hole, so it's just bending the wire and not actually putting any more pressure on the throwbar. I ran into that a few times when I didn;t have the hole cleaned out quite good enough, but since mine was through foam, it was easy enough to clean it out without taking everything apart. Next time it will be plywood, so not as easy to fix. I'd really like to just stick the servos right to the underside, on their sides, with no special mount, but the laser cut plywood mounts that Tam Valley has make it pretty easy and might be worth the cost. Or an excuse to get a laser cutter and make my own.
I have one I was using for testing that uses the old Tam Valley/Motrak resin mount, stuck right to the button of a Peco turnout. Without removing the spring, it needs hardly any servo movement to move the points. But that would require routing out a huge hole under every turnout to drop it all in from the top. I'd rather use some sort of mount that allows for a bit of an over center action, so it's not the servo holding the points, but the springyness of the wire. Short servo throw and at the ends barely any force back on the servo unless you try to manually pull the points to the opposite side.
One thing - most of these servos can't actually do the whole range allowed by the Servo library. I find most of them can only go about 15 to 165 tops, try to go any closer to 0 or 180 and they just stall and draw high current. Default values in my prototype at 40 to 140, and that's actually too far for the directly mounted example I mentioned above. It never reaches either limit without buzzing loudly, so i quickly pulled it and connected a loose servo that could turn freely. Final numbers won't come until I decide on a mount and mock it up with a real turnout.
You aren't doing anything wrong. The onl way to get a servo to 0 current draw is to cut the power off completely (Servo.disconnect() in Arduino does not cut the power, just the signal). Ordinarily a servo always draws some power as it maintains position. How much depends on how hard it has to keep pushing, up to the limit of the servo. I use those same servos - and they cna draw pretty much current if jammed up. Just try to gently force one out of position once it reaches the end of travel. They buzz loudly and current spikes way up. Not going to harm the Arduino, the power does not come from the signal line, and you should not power more than one servo from the Arduino's on board regulator. If you're only getting 4ma then they won't heat up, and you've got that tweaked about as close to perfect as you can.
The rest is all mechanical. The way you are pivoting the actuating wire, the distance between that fulcrum and the servo vs the distance between the fulcrum and the throwbar, and the thickness of the music wire used all determine just how hard you have to push the servo to maintain a good point/stock rail contact. I don;t use ver heavy wire, .032 was fine for Atlas turnouts through 4" of extruded foam plus cork roadbed on top. I could move the points and when I let them go they would snap against the stock rail, yet the servo was quiet. I used the Tam Valley controllers on that layout, and never had a problem with the servos heating up. Never measured the current draw. I had about 10, running on the track power bus of my Zephyr (rails were powered from a separate booster). Your current readings are well below what my prototype is drawing, at least according to my bench power supply. I get nearly 200ma just sitting there, no servos moving (and they aren;t physically hooked to anything either, just sitting on the bench). That's with all relays de-energized as well. When the relays move it spikes higher - I had to set the current limit over 500ma to keep it from tripping to constant current mode. Not sure what the Nano draws, I have 2 LEDs o at all times at 13ma each, whatever the servos draw when not stressed, and there are 8 inputs all using the internal pullups for now. ANd the flashing hearbeat LED. No other loads when the turnouts are both in normal positon - that releases both relays, since I configured it such that the most common position would have no power to any of the relay coils. I'm not worried, I have 3 amp capacity buck convertors to feed each unit from the aux power bus. I thnk the final circuit with a plain ATMega 328 will draw less.