# Property of PMA Electrical Solutions, LLC. # # AutomationDirect DURApulse GS20 and GS20X, built-in Modbus RTU on the RJ-45 # serial port. No option card assumed — the GS20A-CM-ENETIP/EIP2 Ethernet card # is a different path and is not covered here. # # GS20 and GS20X share one manual and one register map; the GS20X is the # NEMA 4X enclosure variant of the same drive. Nothing else in the GS family is # claimed: GS10, GS30 and GS4 are separate manuals and separate profiles. # # SOURCE — AutomationDirect primary documentation only. automationdirect.com # and cdn.automationdirect.com are the vendor domain for the DURApulse brand. # "DURApulse GS20 & GS20X Drive User Manual", 1st Edition, Rev. E # Chapter 5, Serial Communications # pp. 5-5 the worked address examples that prove the formula # pp. 5-6 – 5-7 status addresses and the full error code table below # p. 5-8 command addresses 2000H / 2001H / 2002H # p. 5-9 minimum parameter settings for serial control # Chapter 4, AC Drive Parameters # P01.00, P09.00–P09.04 ranges, defaults and addresses # # ADDRESSING — 0-BASED PROTOCOL ADDRESSES, AND THE MANUAL'S "DEC" COLUMN IS A # TRAP. Every address table in Chapter 5 prints three columns: Hex, Dec, Octal. # The HEX column is the 0-based protocol address. The DEC column is the PLC # 4xxxx form, i.e. 40001 + hex, and using it as a protocol address puts every # read 40001 registers too high. # Status monitor 2101H = 8449 decimal, printed in the Dec column as 48450 # 0x2101 + 40001 = 48450, which is exactly the Dec column. Confirmed. # # Parameter addresses are derived from the parameter number: # 0-based protocol address = (group << 8) | index i.e. hex 0xGGII # The manual works this twice on p. 5-5: "Acceleration time 1 is parameter # P01.12. 12 = 0x0c. 0100 + 0c = 0x010C = 0268 decimal", and P01.13 -> 0x010D. # Chapter 4 confirms it independently: P09.01 is listed at hex 0901 with dec # 42306, and 0x0901 = 2305, 2305 + 40001 = 42306. # P01.00 Max frequency -> 0x0100 = 256 # P09.01 Baud rate -> 0x0901 = 2305 # # ############ RESET IS NOT ON THE RUN REGISTER — THIS ONE HAS BITTEN US ###### # Command word 0x2000 bits 1-0 are: 00B no function, 01B Stop, 10B RUN, # 11B JOG+RUN. So writing 0x0002 to 0x2000 is a RUN COMMAND, not a reset. # The reset lives on its own register, 0x2002 bit 1. # This is the same trap that shipped once on the Delta MS300, and # Profile::validate() now rejects a reset aimed at the run register. The # finding here was sourced independently from the GS20 manual p. 5-8, NOT # carried across from delta_ms300.toml — GS drives are built on Delta hardware # but CLAUDE.md forbids cross-family claims, and nothing in this file came from # that profile. # ############################################################################# # # DRIVE-SIDE SETUP — without these the drive ACKs writes and ignores them # (manual p. 5-9, "Minimum AC Drive Parameter Settings For Serial Communication"): # P00.21 = 2 1st source of operation command = RS-485 # P00.31 = 2 2nd source of operation command = RS-485 # P00.20 = 1 1st source of frequency command = RS-485 # P00.30 = 1 2nd source of frequency command = RS-485 # P09.00 communication address, 1–254 # P09.01 transmission speed, default 9.6 kbps # P09.04 protocol/format, default 15 # Read back 0x2101 bit 8 (master frequency by comms) and bit 10 (operation # command by comms) to confirm the drive actually handed control to the port. name = "AutomationDirect DURApulse GS20 / GS20X" vendor = "AutomationDirect" device_type = "vfd" verified = false probe_register = 0x2101 # status monitor, readable whenever the port is alive [connection] summary = "Choose the built-in RS-485 channel at either access point, or install the documented Ethernet card for Modbus TCP." warnings = ["The screw terminals and serial RJ45 are the same RS-485 bus. Use one wiring topology, not two masters."] [[connection.methods]] id = "control_terminals_rs485" title = "Built-in RS-485 screw terminals" transport = "serial" hardware = "built_in" port = "GS20(X) control terminals SG+, SG-, and SGND" connector = "Three control-terminal positions" adapter = "Isolated USB-to-RS-485 adapter, 2-wire half-duplex" cable = "Belden 9842, AutomationDirect Q8304-1, or equivalent shielded RS-485 cable" parts = ["Isolated two-wire RS-485 adapter", "Belden 9842, AutomationDirect Q8304-1, or equivalent shielded RS-485 cable"] steps = ["Lock out line and control power, wait the vendor-specified discharge time, and verify the DC bus is de-energized before opening a cover or landing conductors.", "Land the positive and negative data conductors exactly as shown; connect the cable shield or signal common only where the method identifies it.", "Route the communications cable away from motor and mains conductors. Terminate the two physical ends of a long multidrop trunk only.", "Restore control power with the motor area clear, configure the drive from its keypad, then verify identity and read-only monitor data before enabling remote control."] setup = ["Set P00.20/P00.30=1, P00.21/P00.31=2, P09.00=node, P09.01=baud, and P09.04 to the selected RTU format."] warnings = ["RS-485 A/B naming is not consistent between manufacturers. Follow the + and - signal mapping in this guide, not the letter printed on an unfamiliar adapter.", "Do not use protective earth as the data return unless the vendor drawing explicitly calls for it."] source = "AutomationDirect DURApulse GS20/GS20X User Manual, chapter 5 pp. 5-8 to 5-13 (shared SG terminals/RJ45, pin mapping, cabling and setup), Appendix B pp. B-15 to B-19 (GS20A-CM-ENETIP/EIP2 Modbus TCP)" [[connection.methods.wiring]] adapter = "Adapter D+ / +" device = "SG+" signal = "RS-485 data positive" required = true [[connection.methods.wiring]] adapter = "Adapter D- / -" device = "SG-" signal = "RS-485 data negative" required = true [[connection.methods.wiring]] adapter = "Adapter signal ground / shield" device = "SGND" signal = "Signal reference or shield drain" required = false [[connection.methods]] id = "rj45_rs485" title = "Built-in RJ45 Modbus RTU" transport = "serial" hardware = "built_in" port = "GS20(X) RJ45 Serial Comm Port" connector = "RJ45 wired as RS-485, not Ethernet" adapter = "Isolated USB-to-RS-485 adapter, 2-wire half-duplex" cable = "Straight-through RJ45 patch cable to a breakout or another same-model drive" parts = ["Isolated two-wire RS-485 adapter", "Straight-through RJ45 patch cable to a breakout or another same-model drive", "Field-terminated modular plug or documented breakout adapter"] steps = ["Lock out line and control power, wait the vendor-specified discharge time, and verify the DC bus is de-energized before opening a cover or landing conductors.", "Land the positive and negative data conductors exactly as shown; connect the cable shield or signal common only where the method identifies it.", "Route the communications cable away from motor and mains conductors. Terminate the two physical ends of a long multidrop trunk only.", "Restore control power with the motor area clear, configure the drive from its keypad, then verify identity and read-only monitor data before enabling remote control."] setup = ["Set P00.20/P00.30=1, P00.21/P00.31=2, P09.00=node, P09.01=baud, and P09.04 to the selected RTU format."] warnings = ["A modular communications jack is not automatically Ethernet. Never connect it to a LAN switch unless this guide explicitly identifies a Modbus TCP method.", "RS-485 A/B naming is not consistent between manufacturers. Follow the + and - mapping shown here.", "Do not use a crossover cable."] source = "AutomationDirect DURApulse GS20/GS20X User Manual, chapter 5 pp. 5-8 to 5-13 (shared SG terminals/RJ45, pin mapping, cabling and setup), Appendix B pp. B-15 to B-19 (GS20A-CM-ENETIP/EIP2 Modbus TCP)" [[connection.methods.wiring]] adapter = "Adapter D+ / +" device = "RJ45 pin 5 (SG+)" signal = "RS-485 data positive" required = true [[connection.methods.wiring]] adapter = "Adapter D- / -" device = "RJ45 pin 4 (SG-)" signal = "RS-485 data negative" required = true [[connection.methods.wiring]] adapter = "Adapter signal ground / shield" device = "RJ45 pin 3 (SGND)" signal = "Signal reference or shield drain" required = false [[connection.methods]] id = "gs20_ethernet_card" title = "GS20A-CM-ENETIP/EIP2 Modbus TCP option" transport = "tcp" hardware = "option" port = "RJ45 Ethernet port(s) on the installed GS20A communications card" connector = "RJ45 Ethernet" adapter = "Computer Ethernet interface or Ethernet switch" cable = "Category 5e or better Ethernet patch cable" parts = ["GS20A-CM-ENETIP or GS20A-CM-EIP2 communications card", "Ethernet patch cable"] steps = ["Lock out and isolate the equipment before fitting an option module or opening the enclosure.", "Install and fully seat the specified communications module according to its installation instructions.", "Connect the computer or switch with the specified Ethernet cable; keep it separated from motor and mains wiring.", "Restore control power, assign an IP address on the same subnet, discover the device, and verify read-only data before enabling network control."] setup = ["Install the supported GS20A-CM-ENETIP or EIP2 card, set P09.74 to a mode that enables Modbus TCP, and configure P09.76-P09.95 IP parameters."] warnings = ["An Ethernet link does not make remote motor control safe. Verify the drive-side communication-loss action and the independent hardwired stop circuit before commanding run.", "The Ethernet option occupies a separate path from the built-in serial port; do not plug Ethernet into the serial RJ45."] source = "AutomationDirect DURApulse GS20/GS20X User Manual, chapter 5 pp. 5-8 to 5-13 (shared SG terminals/RJ45, pin mapping, cabling and setup), Appendix B pp. B-15 to B-19 (GS20A-CM-ENETIP/EIP2 Modbus TCP)" # CHECK THE PARITY BEFORE BLAMING THE WIRING. Chapter 4: P09.04 [COM1 # Communication Protocol] default is 15, which the manual's own option list # decodes as "8, O, 1 (RTU)" — EIGHT DATA BITS, ODD PARITY, ONE STOP BIT. # This drive does not ship 8N1. P09.01 [COM1 Transmission Speed] default is # 9.6 kbps. Both are freely changeable, so confirm against the unit. [serial] baudrate = 9600 parity = "O" stopbits = 1 [monitor] # 0x2101, manual pp. 5-7. Bits 1-0 are a two-bit operation-status FIELD, not # two independent flags: 00B stopped, 01B decelerating, 10B standby, # 11B operating. They are named individually here so the UI can show them, but # read them as a pair. Bits 4-3 are likewise a direction field: 00B FWD running, # 01B REV->FWD, 10B FWD->REV, 11B REV running. status_word = { address = 0x2101, bits = { 0 = "RunBit0", 1 = "RunBit1", 2 = "JogCommand", 3 = "DirBit0", 4 = "DirBit1", 8 = "FreqFromComms", 9 = "FreqFromAnalogOrTerminal", 10 = "CommandFromComms", 11 = "ParametersLocked", 12 = "KeypadCopyEnabled" } } # 0x2100 packs BOTH codes into one register: high byte warning, low byte error. # Exposed raw here; [faults] below masks it. fault_and_warning = { address = 0x2100 } frequency_command = { address = 0x2102, scale = 0.01, unit = "Hz" } output_frequency = { address = 0x2103, scale = 0.01, unit = "Hz" } # 0x2104. WATCH THE DECIMAL SHIFT: the manual states this is XX.XX A, but # "when the current is higher than 655.35, it automatically shifts one decimal # place as (XXX.X A)" and the high byte of 0x211F reports which is in force. # The 0.01 scale below is correct only below 655.35 A, which covers every GS20 # frame — but a profile reused on a larger drive would read ten times low. motor_current = { address = 0x2104, scale = 0.01, unit = "A" } dc_bus_voltage = { address = 0x2105, scale = 0.1, unit = "V" } output_voltage = { address = 0x2106, scale = 0.1, unit = "V" } multi_step_speed_step = { address = 0x2107 } counter_value = { address = 0x2109 } power_factor_angle = { address = 0x210A, scale = 0.1 } output_torque = { address = 0x210B, scale = 0.1, unit = "%" } motor_speed = { address = 0x210C, unit = "rpm" } # Command word 0x2000, manual p. 5-8, WRITE ONLY: # bits 1-0 00B no function, 01B Stop, 10B Run, 11B Jog+Run # bits 3-2 reserved # bits 5-4 00B no function, 01B FWD, 10B REV, 11B change direction # bits 7-6 accel/decel pair select # bits 11-8 multi-step speed select # bit 12 enable bits 6-11 # bits 14-13 operation source override [control] command_register = 0x2000 # Reset is on its OWN register — see the block comment in the header. 0x2002: # bit 0 External Fault on, bit 1 Reset, bit 2 Base Block on, bit 5 fire mode # Because this is not the run register, no reset value here can start the motor. reset_register = 0x2002 step_delay = 0.05 # bits 1-0 = 01B Stop. Ramps per the configured decel time. stop = [0x0001] # bits 1-0 = 10B Run, bits 5-4 = 01B FWD. 0x0002 | 0x0010 run_fwd = [0x0012] # bits 1-0 = 10B Run, bits 5-4 = 10B REV. 0x0002 | 0x0020 # Direction is a genuine command-bit field here, not the sign of the reference, # so reverse is a distinct word rather than a duplicate of forward. run_rev = [0x0022] # Frequency command, 0x2001, format XXX.XX Hz per p. 5-8. speed_register = 0x2001 speed_scale = 0.01 speed_unit = "Hz" # This is the REGISTER full-scale — P01.00 [Maximum Operation Frequency of # Motor 1] has range 0.00–599.00 Hz — not a policy limit. That is the # convention the sibling GS profiles and delta_ms300 use, and what # speed_scales_are_not_inverted expects. The operative ceiling is whatever # P01.00 is actually set to; its factory default is 60.00 Hz (50.00 on 50 Hz # builds). If this machine should never see more than 60 Hz, enforce that at # P01.00 on the drive, not here. max_ref = 599.0 # Bit 1 of 0x2002, per reset_register above. reset = [0x0002] # ############ THIS DRIVE KEEPS RUNNING WHEN THE MASTER GOES QUIET ########### # Chapter 4, P09.02 [COM1 Transmission Fault Treatment] options are # 0 warn and continue, 1 fault and ramp to stop, 2 fault and coast to stop, # 3 no warning, no fault, and continue operation # and THE FACTORY DEFAULT IS 3. P09.03 [COM1 Time-out Detection] defaults to # 0.0 s, i.e. detection disabled. So a stock GS20 behaves like the Danfoss and # the Eaton SVX9000, not like the Schneider ATS48: lose the master mid-run and # the motor keeps turning with no fault raised. # Set P09.03 to a few seconds and P09.02 to 1 or 2 before using this profile # for sustained control. # Once the timeout IS enabled, the failure flips direction: the manual warns # that on a CE10 timeout "the error remains even if the transmission status # returns to normal, and is not cleared automatically. In this case, set a # reset command (Reset) to clear the error." So it latches and needs the reset # on 0x2002 above. # ############################################################################# # # ############ NO [control.watchdog], AND THAT IS THE SAFE CHOICE ############ # Declaring a watchdog spawns the keepalive in control.rs, which periodically # re-writes the LAST COMMANDED control word. On this drive that is a hazard # with no matching benefit: # # - No benefit at factory defaults. The keepalive exists so a drive that # faults on silence does not trip mid-run. The GS20 does not fault on # silence out of the box (P09.03 = 0.0, P09.02 = 3, above), so there is # nothing to keep alive. # - Real hazard if the local STOP key is in play. P00.32 [Digital Keypad # STOP Function] defaults to 0 = "STOP key disabled" whenever the command # source is not the keypad, which is exactly the RS-485 setup this profile # assumes. An installer who sets P00.32 = 1 to give the operator a working # local stop then gets a drive where a keepalive tick re-asserts the stale # RUN word over that stop. The GS1 manual warns about this interaction in # terms; the same refresh mechanism applies here. # # So this profile ships without a watchdog, which sets no_watchdog_backstop in # ArmedInfo and tells the UI the truth: "stop refreshing and the drive stops # itself" DOES NOT hold for this drive. Disarm still sends a real stop first. # # IF you enable the drive-side timeout (P09.04-equivalent P09.03 > 0 with # P09.02 = 1 or 2), the drive WILL trip on silence and a keepalive becomes # necessary. Add it then, and resolve the P00.32 interaction on the machine: # [control.watchdog] # interval_ms = 500 # fault = "58 CE10 PC Communication Time Out" # ############################################################################# # 0x2101 bit 0. The status field is 00B stopped, 01B decelerating, 10B standby, # 11B operating — so bit 0 alone is set for {decelerating, operating}, which is # exactly "the motor is being driven". Bit 1 would instead be set for # {standby, operating} and would report a stopped-but-ready drive as running. [running_check] register = 0x2101 bit = 0 # 0x2100 low byte is the error code; the high byte is a WARNING code and must # be masked out or an active warning renames the fault. That is what mask is # for. 0x0611 (= P06.17 Fault Record 1, per the manual's own note on p. 5-6) is # the stored most-recent fault and is offered as history. # Fault Records 2-6 are presumably P06.18-P06.22 -> 0x0612-0x0616, but the # parameter chapter text for group 06 did not extract and that was NOT # confirmed, so those addresses are deliberately absent rather than inferred. [faults] register = 0x2100 mask = 0x00FF history = [0x0611] # Manual pp. 5-6 – 5-7, transcribed complete. Codes listed by the vendor as # "reserved" (25, 29, 32, 42-47, 65-71, 75, 85-89, 91-96, 98, 100) are omitted. [faults.codes] 0 = "No Error" 1 = "ocA over-current during accel" 2 = "ocd over-current during decel" 3 = "ocn over-current during normal speed" 4 = "GFF ground fault" 5 = "occ IGBT short circuit" 6 = "ocS over-current during stop" 7 = "ovA over-voltage during accel" 8 = "ovd over-voltage during decel" 9 = "ovn over-voltage during normal speed" 10 = "ovS over-voltage during stop" 11 = "LvA low voltage during accel" 12 = "Lvd low voltage during decel" 13 = "Lvn low voltage during normal speed" 14 = "LvS low voltage during stop" 15 = "OrP input phase loss" 16 = "oH1 IGBT overheat 1" 17 = "oH2 capacitor overheat 2" 18 = "tH1o thermistor 1 open" 19 = "tH2o thermistor 2 open" 20 = "PWR power reset off" 21 = "oL overload, 150% for 1 min, inverter" 22 = "EoL1 motor 1 thermal overload" 23 = "EoL2 motor 2 thermal overload" 24 = "oH3 motor overheat, PTC" 26 = "ot1 over torque 1" 27 = "ot2 over torque 2" 28 = "uc under current" 30 = "cF1 EEPROM write error" 31 = "cF2 EEPROM read error" 33 = "cd1 U phase current sensor detection error" 34 = "cd2 V phase current sensor detection error" 35 = "cd3 W phase current sensor detection error" 36 = "Hd0 CC hardware logic error 0" 37 = "Hd1 OC hardware logic error 1" 38 = "Hd2 OV hardware logic error 2" 39 = "Hd3 OCC hardware logic error 3" 40 = "AuE motor auto tune error" 41 = "AFE PID feedback loss" 48 = "ACE analog input signal loss" 49 = "EF external fault" 50 = "EF1 emergency stop" 51 = "bb base block" 52 = "Pcod password error" 53 = "ccod software code lock" 54 = "CE1 PC command error" 55 = "CE2 PC address error" 56 = "CE3 PC data error" 57 = "CE4 PC slave error" 58 = "CE10 PC communication time out" 59 = "CP10 PC keypad time out" 60 = "bf braking transistor fault" 61 = "ydc Y-delta connection error" 62 = "dEb decel energy backup error" 63 = "oSL over slip error" 64 = "ryF electromagnet switch error" 72 = "STL1 STO loss 1, STO1~SCM1 internal hardware detect error" 73 = "S1 ES1 emergency stop" 74 = "Fire in fire mode" 76 = "STO safe torque off function active" 77 = "STL2 STO loss 2, STO2~SCM2 internal hardware detect error" 78 = "STL3 STO loss 3, both STO channels" 79 = "Uoc U phase short" 80 = "Voc V phase short" 81 = "Woc W phase short" 82 = "UPHL U phase loss" 83 = "VPHL V phase loss" 84 = "WPHL W phase loss" 90 = "FStp PLC force stop" 97 = "CD10 Ethernet card timeout" 99 = "TRAP CPU command error" # NO [parameters] BLOCK YET. The parameter space is dense and contiguous by # construction — (group << 8) | index means group 01 occupies 0x0100-0x01FF and # so on — but it interleaves read-only monitoring parameters, reserved indices # and write-protected entries with the writable settings, and Chapter 4's group # tables did not extract as text for every group. A restore that walked a whole # group blindly would NAK on the read-only entries at best. Establish the R/W # ranges from Chapter 4's per-parameter tables before adding this block. # # NO [rating] BLOCK: no read-only frame-size register was confirmed in the # chapters read, so there is nothing to compare against before a restore.