Modbus Spindle Control
-
So I've been playing around today with the very early Modbus support in M260.1 and M261.1 and managed to start, stop and set frequency on my VFD to run the spindle on my CNC machine.
This is the main reason why I've been interested in Modbus support in RRF but it brings up a question - is there any solid plan within the Duet team of how controlling the spindle via Modbus might integrate into the existing spindle setup via M950.
It would be easy to have custom macros (I use M3.9 and M5.9 already to implement delays to make sure the spindle is running at the target speed) that call M260.1 to control the spindle from a post processed gcode file, but this would not integrate into DWC or the existing spindle commands in any way.
I wonder if it would make sense to allow a dummy spindle to be defined using M950, and allow M3 and M5 commands to be overridden if a relevant macro file exists (e.g. spindle-start.g, spindle-stop.g).
That would allow a Modbus controlled spindle to be controlled using standard M3 and M5 commands, which would allow manual control via DWC as well.
Thoughts appreciated!
-
@NineMile I plan to introduce a more direct way to control spindles via Modbus but I have yet to design that support. How much configuration of the Modbus commands to control the spindle do you think we need? Do you think that using macros in some way will always be fast enough? What about emergency stop?
-
@dc42 said in Modbus Spindle Control:
@NineMile I plan to introduce a more direct may to control spindles via Modbus but I have yet to design that support. How much configuration of the Modbus commands to control the spindle do you think we need? Do you think that using macros in some way will always be fast enough? What about emergency stop?
Interesting questions.
My experience of Modbus so far is limited to a single device, (Shihlin SL3 VFD that is supplied with the 240v Milo kits), but my understanding is there is no consistency between devices - so while this device can be run using the following commands, there is no way that will extend to other devices.
; Reset inverter M260.1 P1 A1 F6 R4353 B38550 ;Set speed to 22000 rpm M260.1 P1 A1 F6 R4098 B22000 ; Run forwards M260.1 P1 A1 F6 R4097 B2 ; Run backwards M260.1 P1 A1 F6 R4097 B4 ; Stop M260.1 P1 A1 F6 R4097 B0
I don't think this can be generalised down to all Modbus controlled VFDs. It isn't even possible, I think, to generalise that a single VFD model will take the same registers and inputs as another of the same model, configured in a different way.
For example, above I configure the spindle speed in RPM - this took me a while to work out, and it's because I configured the display value on my VFD to calculate and output the RPM rather than outputting the frequency. That seems to change how the VFD wants the input speed to be set as a rotational speed rather than a frequency as well. I can also change whether the registers are displayed as grouped (e.g. 07-01) or parameter numbers (e.g. P.36 - these refer to the same setting), and this changes the register addresses that are used to control the VFD over Modbus
If this were not done as user-configurable macros then I think at the very least it would need to be possible to provide a list of register or coil addresses and their intended values for each function (forwards, backwards, set speed etc), that would be written in order, but then that brings up the question of what to do if one of the writes fails - do we have a recovery command or something like that (e.g. it might send a reset command over Modbus).
I think this is how LinuxCNC's mb2hal component is configured, where it has a set of transactions configured by the operator that are triggered in response to changes in pin states.
On the emergency stop front - my VFD has an emergency stop input on the same register as the Forward / Reverse run, but I think I would always want to have a physical emergency stop circuit using one of the digital inputs on the VFD. I think it could be used in combination with EStop input sent via Modbus if necessary, so there is multiple channels for emergency stop input.
Reading further into my VFD manual, it seems to have a communications "dead-man" alarm - where it will trigger an alarm state if it does not receive communication over Modbus for a configurable number of seconds.
My gut feel is that when we have commanded the spindle to start, I would read the spindle frequency from
daemon.g
(which has proved to be good enough to implement Variable Spindle Speed Control at 500ms intervals), and this would be the mechanism to keep the VFD in a healthy state.The only time I have noticed significant pauses so far has been on running a
G17
,G18
orG19
command during job processing where memory needed to be allocated to process arc moves (I assume this was triggering garbage collection, hence the longer pauses). An extended pause might cause issue with this "dead-man" behaviour but as long as it were configured reasonably then I don't see a particular reason why Macro control wouldn't be suitable speed wise, at least for CNC Milling circumstances.We're dealing with seconds to spin up and stop the spindle rather than milliseconds, and we can wait for as long as necessary for the spindle to change speed - we just don't start processing moves until the spindle has reached the target speed (which is much easier to monitor than with the analog control).
Whether or not this applies to other uses of RRF I'm not sure.
-
So I decided to try using
daemon.g
to control the VFD using theM260.1
commands, by reading the desired speed and direction out of the object modelspindles[]
- I wanted to see how responsive this felt, given the question around controlling it via macros.My
daemon.g
code runs in a tight loop with a 500ms delay and is essentially the following:; Read status bits and requested frequency M261.1 P1 A1 F3 R4097 B2 V"spindleInputState" ; Read the output frequency, current, voltage ; M261.1 P1 A1 F3 R4099 B3 V"spindleOutputState" ; Read any error codes ;M261.1 P1 A1 F3 R4103 B2 V"spindleErrorState" ; spindleInputState[0] is a bitmask of the following values: ; b15:during tuning ; b14: during inverter reset ; b13, b12: Reserved ; b11: inverter E0 status ; b10~8: Reserved ; b7:alarm occurred ; b6:frequency detect ; b5:Parameters reset end ; b4: overload ; b3: frequency arrive ; b2: during reverse rotation ; b1: during forward rotation ; b0: running var spindleRunning = { mod(floor(var.spindleInputState[0]),2) == 1 } var spindleForward = { mod(floor(var.spindleInputState[0]/pow(2,1)),2) == 1 } var spindleReverse = { mod(floor(var.spindleInputState[0]/pow(2,2)),2) == 1 } var spindleAtFrequency = { mod(floor(var.spindleInputState[0]/pow(2,3)),2) == 1 } var currentFrequency = { var.spindleInputState[1] } if { spindles[0].state == "stopped" && var.spindleRunning } M260.1 P1 A1 F6 R4097 B0 M99 if { var.currentFrequency != spindles[0].active && spindles[0].active > 0 } M260.1 P1 A1 F6 R4098 B{spindles[0].active} if { result != 0 } echo { "Failed to set spindle speed to " ^ spindles[0].active ^ "RPM" } M999 if { spindles[0].state == "forward" && !var.spindleForward } M260.1 P1 A1 F6 R4097 B2 elif { spindles[0].state == "reverse" && !var.spindleReverse } M260.1 P1 A1 F6 R4097 B4
I recorded a video of this in action here - the only discernible delay I could see was coming from the deliberate
G4
indaemon.g
to stop it hotlooping.So my gut feel is that macro control using system macros that do not rely on
daemon.g
to send the commands will be more than fast enough for my purposes. -
@dc42 Have done some further testing and I have some thoughts.
I think it would be helpful if errors from
M261.1
were suppressed when theV
parameter is given.Example: I read the status of the VFD using
M261.1 V"..."
fromdaemon.g
, but say for example my VFD is not turned on, then I end up spamming the console full ofError: no or bad response from Modbus device
.Given that the variable is inserted with a null value before the transfer is initiated, we can already check if the request failed or not, and the I2c side of the command looks like it suppresses the "nothing" reply when setting a variable.
I've made a PR to suppress the output here.
I'm still having some timing issues if I don't use
G4 P1
between each sequential read but I'm unsure why this is happening - I'm using an stm32 board, but I wonder if the VFD itself requires an increased delay between frames for whatever reason. -
@NineMile How recent is the version of the stm32 source base you are using? I've added some of the low level UART changes that may help the timing issues (in coren2g), but I have not yet merged in the very latest changes in which I think David has adjusted the timing.
-
@gloomyandy ~August 18th afternoon for both RepRapFirmware and CoreN2G - git reports both up to date with origin (your branches).
I've applied David's changes myself to test but it didn't seem to resolve the issue
I still have the oscilloscope hooked up but haven't had a chance to test yet, should be able to get both successful and unsuccessful traces a bit later.
-
@NineMile Yep that should have the UART changes present in it. They should allow the packet to be constructed and then sent all in one continuous burst (which may not have been happening without those changes). However I've not been able to test them. I'll be updating things later today with the latest changes (there is a lot happening in 3.6 at the moment so it take s a while to update and test things).
Of course the other thing is that there is no knowing how well these various devices actually follow the standards!
-
@dc42 will we get events for ModBus unexpectedly disconnecting to allow for safe disengagement?
-
@oliof no you won't, because the only way to tell if a Modbus device is responding is to send a command to write or read a register and check whether the M260.1 or M261.1 command returns success - and you can do that yourself.
When we start using Modbus to implement critical functions without using M260.1 or M261.1 such as changing spindle speed, then we may introduce events to handle failure of those operations.
-
Just wanted to update to say I've been using
daemon.g
to control my VFD over modbus for a while now and it feels like it's working well. The error messages not being suppressed when writing to a variable is a bit of a pain but aside from that, it feels like a relatively robust way to do it.For clarity, I still have the spindle configured to use enable / direction / PWM pins but these are not connected. I read the spindle state in
daemon.g
and send the relevant Modbus requests to start it and control the speed.I've had a 2 second comms timeout set on the VFD so if there's no Modbus status check within 2 seconds the VFD will stop automatically.
There's a lot of functionality I could add to this to e-stop / pause / whatever if the VFD reports the spindle stopped or at high load or is completely unresponsive, but for the moment I'm pretty happy with the behaviour.
-
So I've been thinking about how spindle control could be integrated directly into RRF in a manner that wouldn't require a HAL to be written in C++ for each type of VFD, and the approach that seems to make sense thus far is to implement a modbus device type that can be assigned to an address on a configured aux port, and then assigned to a spindle of type modbus.
The modbus device type stores a limited number of read and write commands - reads are run on a configurable interval and populate data into the object model, one or more write commands can be configured to act as the actions to take to run a spindle forward, in reverse or to stop it. It would also be possible to run stored commands manually (think of like how
M261.1
currently works but rather than specifying all the details you just pick the command number to run).The nice thing about abstracting the modbus device out is that it could also work to monitor and manage non VFD devices using stored commands.
; Configure Aux 1 to Modbus / UART M575 P1 B57600 S7 ; Create Modbus device 2 at station 80 on Aux 1, ; waiting a minimum of 10ms between requests. ; Some modbus devices have defined processing ; delays between requests that should not be violated M610 P1 I2 A80 D10 ; Assign read commands to device 2. Maximum of 5? ; NOTE: Commands are all named, this is purely for ; readability purposes in the object model. ; Read 5, 16 bit registers from 0x1001 every 500ms. ; These values would be available in the object model. ; P0 is the command index that can be used later ; to refer to this command. M611.1 I2 P0 S"spindleState" R4097 B5 D500 ; Read 1, 16 bit register from 0x1003 every 250ms. ; Multiply the value by a factor of 60 to convert ; this to RPM M611.1 I2 P1 S"spindleSpeed" R4099 B1 D250 F60 ; Read 1, 16 bit register from 0x101B every 500ms. M611.1 I2 P2 S"spindlePower" R4123 B1 D500 ... ; Assign write commands to device 2. Maximum of 5? ; Reset M611.2 I2 P0 S"reset" R4353 B38550 ; Run Forward M611.2 I2 P1 S"runForward" R4097 B2 ; Run Reverse M611.2 I2 P2 S"runReverse" R4097 B4 ; Stop M611.2 I2 P3 S"stop" R4097 B0 ; Emergency Stop M611.2 I2 P4 S"emergencyStop" R4097 B127 ; Set RPM ; This command is special as it does not ; define a B-value - the B argument must ; be given if this command is run manually, ; and would be passed from the spindle control ; code. ; The F argument is a scale factor to adjust ; the input value into the right unit for the ; VFD, in this case from RPM to Hz. M611.2 I2 P5 S"setRPM" R4098 F0.01667 ; Create Spindle 0 as a Modbus spindle, ; using device 2. M950 P0 T2 I2 ; At this point the spindle will exist and RRF ; will start polling the Modbus device for each ; of the read registers defined. It will not ; be controllable because no write commands have ; been assigned to the spindle yet. ; F is the command number(s) to run in order ; to start the spindle. Assign write command ; 1 to start the spindle forwards. ; R is the command number(s) to run in order ; to run the spindle in reverse. ; S is the command number(s) to run in order ; to stop the spindle. ; V is the command number(s) to run in order ; to change the speed of the spindle M611.3 P0 F1 R2 S3 V5 ; For a device that need writes to multiple ; registers to start the spindle, you might ; implement them like the below ; Enable M611.2 I2 P1 S"enableSpindle" R4097 B1 ; Stop M611.2 I2 P2 S"stop" R4097 B0 ; Direction field is part of a bitmask, ; bit 1 set indicates run in reverse ; Forward M611.2 I2 P3 S"setForward" R4098 B0 ; Reverse M611.2 I2 P4 S"setReverse" R4098 B1 ; Configure spindle commands ; For forward, run forward and enable ; For reverse, run reverse and enable ; For stop, run stop etc M611.3 P0 F3:1 R4:1 S2...
I realise this is somewhat complex from a configuration perspective but it means adding support for any modbus-rtu capable device should be doable in configuration only.
If anyone has any suggestions on this, whether it's too complex or whether there's a way to allow this to support non modbus compliant systems as well (e.g. HuanYang VFDs) then I'd love to hear it.
I'm willing to take a stab at implementing this approach if people think this is a reasonable approach.
-
@NineMile Would this work with the VFD discussed over on discord (the HY02D223B I think)?
Personally I'd prefer to extend RRF via external scripts to handle the various spindle operations. I don't think changes in speed or other spindle changes happen so often that doing this would be an issue. So basically have RRF call out to a script (with parameters if needed) for every spindle operation (start, stop, change in speed etc). I'd pair that with adding/extending the existing modbus read/write commands to offer a way to write just a set of bytes with modbus framing and crc. Looking at the LinuxCNC source it seems there are a number of VFDs (like the HY02D223B) which seem to use modbus like communication (including the "normal" modbus crc calculation), but which do not follow the register/coil model of modbus-rtu. So maybe having a way to read/write an arbitrary set of bytes with the required framing and crc would allow scripts to handle the protocol formatting? One advantage of this approach is that in theory you could use the same mechanism to talk to a VFD that does not use modbus style packets at all (though this would require any crc to be calculated by the script).