Skip to content

Instantly share code, notes, and snippets.

@benagricola
Created January 8, 2025 18:13
Show Gist options
  • Save benagricola/b5e432a3923b9789630e81358e46ec7d to your computer and use it in GitHub Desktop.
Save benagricola/b5e432a3923b9789630e81358e46ec7d to your computer and use it in GitHub Desktop.
Modbus spindle control in RRF V3.6 using daemon.g

Overview

Spindle

Configure the spindle as normal - this is required because we're hooking the spindle control so it needs to already be setup with enable, direction and speed ports:

; Configure Spindle for Modbus
M950 R0 T1 C"spindlepwm+spindleen+spindledir" L1999:24000 Q1000

Aux Port

Configure an Aux port for Modbus at the right baud rate:

; Configure RS485 UART on Aux 1 at 38400bps
M575 P1 B38400 S7

Work out how to control the spindle using direct M260.1 (if your VFD is modbus-rtu compliant) or M260.4 (if your VFD uses modbus timing and framing but does not support the register / coil model).

For example with the Shihlin SL3 VFD:

; NOTES:
;   VFD is set to group mode, so parameters are 10000 + param number read as a number. 
;     For example, param 01-01 is 10101 in decimal, or 0x2775. Addresses are different 
;     when the VFD is set to raw parameter mode.
;   VFD has speed configuration set that converts input speed from RPM to frequency.
;     With other VFD types or with a SL3 VFD in default mode you'll likely have to
;     enter a frequency in Hz.
;
; Reset inverter
M260.1 P1 A1 F6 R4353 B38550

;Set Frequency to 22000RPM
M260.1 P1 A1 F6 R4098 B22000

; Run forwards
M260.1 P1 A1 F6 R4097 B2

Daemon

Once you're happy with controlling the spindle manually, you can create a daemon.g file which checks the state of the configured spindle and applies these as M260.x commands, for example with the SL3 VFD:

if { !exists(global.runDaemon) }
    global runDaemon = true
    
while { global.runDaemon }
    G4 P250 ; Minimum interval between daemon runs

    ; 0 = Status Bits, 1 = Requested Frequency, 2 = Output Frequency, 3 = Output Current, 4 = Output Voltage
    M261.1 P1 A1 F3 R4097 B5 V"spindleState"

    ; Note - some VFDs have a minimum processing time for requests. Check the manual to see what this
    ; is - you might need to apply manual pauses between commands to make sure these dont error.
    G4 P1

    ; Read output power
    M261.1 P1 A1 F3 R4123 B1 V"spindlePower"
    ;2f10 0124 30ea

    G4 P1

    ; Read any error codes
    M261.1 P1 A1 F3 R4103 B2 V"spindleErrors"

    G4 P1
    
    ; spindleState[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

    if { var.spindleState == null }
        M99
        
    var shouldRun     = { spindles[0].state == "forward" || spindles[0].state == "reverse" }

    ; Extract current state to variables
    var vfdForward = { mod(floor(var.spindleState[0]/pow(2,1)),2) == 1 }
    var vfdReverse = { mod(floor(var.spindleState[0]/pow(2,2)),2) == 1 }
    var vfdSpeedReached = { mod(floor(var.spindleState[0]/pow(2,3)),2) == 1 }
    var vfdRunning  = { var.vfdForward || var.vfdReverse }
    var vfdInputFreq = { var.spindleState[1] }
    var vfdOutputFreq = { var.spindleState[2] }
    var vfdOutputCurrent = { var.spindleState[3] }
    var vfdOutputVoltage = { var.spindleState[4] }

    var vfdOutputPower = null
    if { var.spindlePower == null }
        set var.vfdOutputPower = 0
    else
        ; Efficiency based on nameplate info
        set var.vfdOutputPower = { var.spindlePower[0] * 0.75 * 10 }
    
    ; If spindle should not run but is running, stop it
    if { !var.shouldRun && var.vfdRunning }
        ; Stop spindle, set frequency to 0
        M260.1 P1 A1 F6 R4097 B0
        G4 P1
        M260.1 P1 A1 F6 R4098 B0
        continue
        
    ; If VFD frequency does not match frequency in RRF, update it
    if { var.vfdInputFreq != spindles[0].active }
        M260.1 P1 A1 F6 R4098 B{spindles[0].active}
        G4 P1

    if { spindles[0].state == "forward" && !var.vfdForward }
        M260.1 P1 A1 F6 R4097 B2

    elif { spindles[0].state == "reverse" && !var.vfdReverse }
        M260.1 P1 A1 F6 R4097 B4

Modify this file to suit your VFD and needs for monitoring the spindle.

@jrlomas
Copy link

jrlomas commented Jan 12, 2025

This is a working configuration for the Huanyang HY02D223B:

; Initialize global variables if they don't exist
if {!exists(global.runDaemon)}
    global runDaemon = true
if {!exists(global.spindleState)}
    global spindleState = "STOP"

; Daemon loop
while {global.runDaemon}
    G4 P250 ; Minimum interval between daemon runs

    ; Read data from the VFD
    M260.4 P1 A1 B{{0x04, 0x03, 0x00, 0x00, 0x00}} R5 V"rawSetFrequency" ; divide by 100 in Hz
    if {var.rawSetFrequency == null}
        M99
    var setFrequency = {var.rawSetFrequency[3] * 256 + var.rawSetFrequency[4]} ; Combine last two bytes

    G4 P10

    M260.4 P1 A1 B{{0x04, 0x03, 0x01, 0x00, 0x00}} R5 V"rawOutFrequency" ; divide by 100 in Hz
    if {var.rawOutFrequency == null}
        M99
    var outFrequency = {var.rawOutFrequency[3] * 256  + var.rawOutFrequency[4]}
    

    ; M260.4 P1 A1 B{{0x04, 0x03, 0x02, 0x00, 0x00}} R5 V"rawOutCurrent" ; divide by 10 in A (amps)
    ; var outCurrent = {var.rawOutCurrent[3] * 256 + var.rawOutCurrent[4]}

    ; M260.4 P1 A1 B{{0x04, 0x03, 0x03, 0x00, 0x00}} R5 V"rawOutRPM" ; rpm
    ; var outRPM = {var.rawOutRPM[3] * 256 + var.rawOutRPM[4]}

    ; M260.4 P1 A1 B{{0x04, 0x03, 0x04, 0x00, 0x00}} R5 V"rawInDC" ; incoming rectified DC Voltage / 10
    ; var inDC = {var.rawInDC[3] * 256 + var.rawInDC[4]}

    ; M260.4 P1 A1 B{{0x04, 0x03, 0x05, 0x00, 0x00}} R5 V"rawOutAC" ; out AC voltage / 10
    ; var outAC = {var.rawOutAC[3] * 256 + var.rawOutAC[4]}

    ; Determine if the spindle should run
    var shouldRun = {spindles[0].state == "forward" || spindles[0].state == "reverse"}

    ; Extract current state to variables
    var vfdForward = {global.spindleState == "CW"}
    var vfdReverse = {global.spindleState == "CCW"}
    var vfdSpeedReached = {var.setFrequency == var.outFrequency}
    var vfdRunning = {var.vfdForward || var.vfdReverse}
    var vfdInputFreq = {var.setFrequency / 100.0}
    var vfdOutputFreq = {var.outFrequency / 100.0}
    ; var vfdOutputCurrent = {var.outCurrent / 10.0}
    ; var vfdOutputVoltage = {var.outAC / 10.0}

    ;M118 P0 S{"vfdForward: " ^ var.vfdForward ^ ", vfdReverse: " ^ var.vfdReverse ^ ", vfdSpeedReached: " ^ var.vfdSpeedReached ^ ", vfdRunning: " ^ var.vfdRunning ^ ", vfdInputFreq: " ^ var.vfdInputFreq ^ ", vfdOutputFreq: " ^ var.vfdOutputFreq }

    ; If the spindle should not run but is running, stop it
    if {!var.shouldRun && var.vfdRunning}

        M260.4 P1 A1 B{{0x05, 0x02, 0x00, 0x00}} R4 ; Set frequency to 0.0Hz
        G4 P10
        M260.4 P1 A1 B{{0x03, 0x01, 0x08}} R3 ; Stop spindle
        if {global.spindleState = "CW"}
            M3 P0 S0
        if {global.spindleState = "CCW"}
            M4 P0 S0
        set global.spindleState = "STOP"
        continue

    var currentRPM = {spindles[0].active}
    var currentHz = {var.currentRPM / 24000.00 * 400.00}

    ; If VFD frequency does not match frequency in RRF, update it
    if {var.vfdInputFreq != var.currentHz}
        var scaledFrequency = {floor(var.currentHz * 100)}
        ; Calculate high and low bytes using division and modulo
        var highByte = {floor(var.scaledFrequency / 256)}            ; Integer division to get the high byte
        var lowByte = {var.scaledFrequency - (var.highByte * 256)} ; Subtract to get the low byte
        M260.4 P1 A1 B{{0x05, 0x02, {var.highByte}, {var.lowByte}}} R4
        G4 P1

    ; Handle spindle direction changes
    if {spindles[0].state == "forward" && !var.vfdForward}
        M260.4 P1 A1 B{{0x03, 0x01, 0x01}} R3
        set global.spindleState = "CW"

    elif {spindles[0].state == "reverse" && !var.vfdReverse}
        M260.4 P1 A1 B{{0x03, 0x01, 0x11}} R3
        set global.spindleState = "CCW"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment