LMCV4-FIBER-M
JCZ LMCV4-FIBER-M is a fiber galvo type laser controlled by Ezcad 2, the LMC boards are all controlled by Ezcad2. The board controls a galvo head laser through the standard XY-100 protocol on a DB25 connection. The board itself costs around $200 USD (Jan 2022). With the similar protocol Ezcad 3 board going for $800 USD (Jan 2022). The additional features of the Ezcad3 are likely not worth the value. The reason why people pay for them is so that they can use Ezcad3 rather than being stuck to using Ezcad2 which like a lot of laser software is disliked by users. In the case of Ezcad, it crashes often and the F1 let me see what my design is doing is right next to the F2 burn the project without the lining up the material buttons. There are some hacked versions of this software which provide dongle-less support and raises the ram limit which is baked into the ram starved program.
Reverse engineering
The reverse engineering of the product has been done (at least initially) https://www.bryce.pw/engraver.html and Jason Dorie (https://lightburn.fider.io/) the latter for inclusion in LightBurn, a highly regarded software program within the laser community.
Command codes are 2 bytes 16 bit little-endian numbers these starting either with high or low bit (0x00 or 0x80) depending on whether it is a commandList command or a single command. In the larger LMC Ezcad3 code set these can sometimes reach into the first byte when the code value exceeds 255 0xFF. The board communications are done through a simple USB connection with several endpoints connecting to:
idVendor=0x9588, idProduct=0x9899
- ep_hodi = 0x01 # endpoint for the "dog," i.e. dongle.
- ep_hido = 0x81 # fortunately it turns out that we can ignore it completely.
- ep_homi = 0x02 # endpoint for host out, machine in. (query status, send ops)
- ep_himo = 0x88 # endpoint for host in, machine out. (receive status reports)
Commands are sent and received in the clear without swizzling or additional control bits.
commandList codes:
command code | name | notes | usage |
---|---|---|---|
0x800d | listJumpTo | ||
0x8001 | listJumpTo | ||
0x8005 | listMarkTo | ||
0x8003 | listLaserOnPoint | ||
0x8006 | listJumpSpeed | ||
0x800c | listMarkSpeed | ||
0x801b | listMarkFreq | (if lasertype == 3) | |
0x800a | listMarkFreq | (if lasertype == 4) | |
0x800b | listMarkFreq | (if lasertype == 4 and 0x800a returned 0). | |
0x8013 | listMarkFreq | (if some_setting2 == 0) | |
0x800a | listMarkFreq | (if some_setting3 != 0) | |
0x8012 | listMarkPowerRatio | ||
0x800b | listMarkPowerRatio | (if lasertype == 0 (CO2)) | |
0x800b | listMarkPulseWidth | ||
0x8026 | listIPGYLPMPulseWidth | ||
0x8007 | listLaserOnDelay | ||
0x8008 | listLaserOffDelay | ||
0x800f | listPolygonDelay | ||
0x8004 | listMarkEndDelay | ||
0x8004 | listDelayTime | ||
0x8004 | listDelayTimeUs | ||
0x8002 | listEndofList | ||
0x801a | listFlyEnable | ||
0x801d | listFlyDelay | ||
0x8011 | listWritrPort | ||
0x8051 | ReadyMark | ||
0x8002 | Run. | ||
0x801c | listDirectLaserSwitch | ||
0x801e | SetCo2FPK | ||
0x8005 | listDirectMarkTo | ||
0x801f | lsFlyWaitInput | ||
0x8021 | listIPGOpenMO | ||
0x8023 | listChangeMarkCount | ||
0x8022 | listWaitForInput | ||
0x8026 | listIPGSetConfigExtend | ||
0x8028 | listFlyEncoderCount | ||
0x8029 | listSetDaZWord | (if this returns 0, listDelayTime is called) | |
0x8050 | listJptSetParam | ||
0x8025 | listEnableWeldPowerWave | ||
0x8024 | listSetWeldPowerWave | ||
0x8006 | ScanBmpPtBuf | (several times) |
Single Commands:
Single commands are 12 bytes long in 6 int16le bytes. The first byte is the command code. Some commands in the DLC ezcad3 codeset move into higher byte. Nothing in the LMC board command set does. These commands are usually followed by GetState which reads 6 bytes of data. This provides response information and state information. These do not use all six int16le are used. The remaining ones are padded with zeros.
name | command code | parm1 | parm2 | parm3 | parm4 | parm5 | usage | |
---|---|---|---|---|---|---|---|---|
DisableLaser | 0x0002 | 0 | 0 | 0 | 0 | 0 | ||
EnableLaser | 0x0004 | 0 | 0 | 0 | 0 | 0 | ||
ExecuteList | 0x0005 | 0 | 0 | 0 | 0 | 0 | ||
GetVersion | 0x0007 | 1 | 0 | 0 | 0 | 0 | ||
GetSerialNo | 0x0009 | 0 | 0 | 0 | 0 | 0 | ||
GetListStatus | 0x000a | 0 | 0 | 0 | 0 | 0 | ||
GetPositionXY | 0x000c | 0 | 0 | 0 | 0 | 0 | ||
GotoXY | 0x000d | x | y | 0 | 0 | 0 | ||
LaserSignalOff | 0x000e | 0 | 0 | 0 | 0 | 0 | ||
LaserSignalOn | 0x000f | 0 | 0 | 0 | 0 | 0 | ||
SetAxisMotionParam | 0x0026 | variable | stack value | 0 | 0 | 0 | 0 | |
MoveAxisTo | 0x0029 | lower 8 bits | 16-24th bits | 0 | 0 | 0 | specified by single variable | |
AxisGoOrigin | 0x0028 | variable | 0 | 0 | 0 | 0 | ||
GetAxisPos | 0x002a | stack value | 0 | 0 | 0 | 0 | ||
SetAxisOriginParam | 0x0027 | variable | stack value | 0 | 0 | 0 | 0 | |
WriteCorTable | 0x0015 | boolean | 0 | 0 | 0 | 0 | This is often followed by a 8 * 63 * 63 byte xy-lookup table | |
ResetList | 0x0012 | 0 | 0 | 0 | 0 | 0 | ||
RestartList | 0x0013 | 0 | 0 | 0 | 0 | 0 | ||
SetControlMode | 0x0016 | stack value | 0 | 0 | 0 | 0 | ||
SetDelayMode | 0x0017 | stack value | 0 | 0 | 0 | 0 | ||
SetMaxPolyDelay | 0x0018 | stack value | 0 | 0 | 0 | 0 | ||
SetEndOfList | 0x0019 | 0 | 0 | 0 | 0 | 0 | ||
SetFirstPulseKiller | 0x001a | stack value | 0 | 0 | 0 | 0 | ||
SetTiming | 0x001c | stack value | 0 | 0 | 0 | 0 | ||
SetPwmHalfPeriod | 0x001e | stack value | 0 | 0 | 0 | 0 | ||
SetPwmPulseWidth | 0x0006 | stack value | 0 | 0 | 0 | 0 | ||
SetLaserMode | 0x001b | stack value | 0 | 0 | 0 | 0 | ||
SetStandby | 0x001d | variable | variable | stack value | 0 | 0 | ||
StopExecute | 0x001f | 0 | 0 | 0 | 0 | 0 | ||
StopList | 0x0020 | 0 | 0 | 0 | 0 | 0 | ||
ReadPort | 0x0025 | 0 | 0 | 0 | 0 | 0 | ||
WritePort | 0x0021 | variable | stack value | 0 | 0 | 0 | ||
WriteAnalogPort1 | 0x0022 | stack value | 0 | 0 | 0 | 0 | ||
WriteAnalogPort2 | 0x0023 | 0 | stack value | 0 | 0 | 0 | ||
WriteAnalogPortX | 0x0024 | variable | stack value | 0 | 0 | 0 | ||
SetFpkParam | 0x0062 | variable | variable | variable | stack_value | 0 | Probably First Pulse Killer | |
SetFpkParam2 | 0x002e | variable | variable | variable | stack_value | 0 | ||
IPG_OpemMO | 0x0033 | stack value | 0 | 0 | 0 | 0 | ||
IPG_GETStMO_AP | 0x0034 | 0 | 0 | 0 | 0 | 0 | ||
ENABLEZ | 0x003a or 0x0039 (if a value is zero) | 0 | 0 | 0 | 0 | 0 | ||
SETZDATA | 0x003b | variable | stack value | 0 | 0 | 0 | ||
SetSPISimmerCurrent | 0x003c | variable | stack_value | 0 | 0 | 0 | ||
GetFlyWaitCount | 0x002b | boolean | 0 | 0 | 0 | 0 | ||
GetMarkCount | 0x002d | boolean | 0 | 0 | 0 | 0 |
IOCLT control codes
And additionally there are calls to a couple different IO events:
See winsdk-7 header file https://github.com/tpn/winsdk-7/blob/master/v7.1A/Include/WinIoCtl.h
MIO_Cmd uses the dwIoControlCode 0x99982014
MIO_GetState uses: 0x99982010
MIO_NewCmd uses: 0x99982024
MIO_Reset uses: 0x99982008
MIO_WriteCmdBuf uses: 0x99982018
MIO_TransferDataZ uses: 0x99982020
MIO_ReadEpprom uses: 0x99982080
MIO_ReadAllEpprom uses: 0x99982090
MIO_ModifyEpprom uses: 0x99982088
MIO_EarseEpprom(sic) uses: 0x9998208c
MIO_WriteEpprom uses: 0x99982084
MIO_EppromSetMark uses: 0x99982094
MIO_EppromGetMark uses: 0x99982098
MIO_EppromSetTimeStamp uses: 0x9998209c
MIO_Verify uses: 0x99982028
MIO_WriteNxp uses: 0x999820c0
MIO_ReadNxp uses: 0x999820c4
External Links
- https://www.bryce.pw/engraver.html - Bryce Schroeder's work on reverse engineering the board
- https://charliex2.wordpress.com/2020/01/31/fibre-laser-arrives-let-the-games-begin/ -- Fantastic blog post on setting up a dummy dll