Exactly How a CNC Works This document will detail how a CNC operating system and stepper motor servo loop operate. Although written from Stepster.exe the document points to the ideal CNC rather than explaining the compromises sometimes evident in Stepster. The readers of this document should be thoroughly familiar with G-codes, at a minimum they should read the NCreadme.txt that accompanies Stepster. The reader may also want to compare the description of functions here to the actual source code for Stepster, but would do well not to copy Stepster too closely as it has many examples of poor programming. Contents 1 Main Menu 2 Manual 3 Linear Move 4 The Parser 5 Manual Data Input 6 Circular Moves 7 Automatic Operation 8 Canned Cycles 1 Main Menu The Main Menu is a means of selecting Auto, Manual or Manual Data Entry modes via "hot keys". If a control panel is fitted it should also be read by the main menu block when the selector switch is placed in "Auto". 2 Manual Operation The block of code for manual operation sets the jog distance, watches for key presses (and button presses) to command moves, and resets axis values to zero when commanded. When a manual incremental jog is commanded the standard linear move loop is used. Continuous jogging would require a separate routine that would watch for the key or button to be released, then decelerate to a stop. If the CNC is working with the machine coordinate system (G53) zeroing an axis sets the "master" position to zero. When the Z axis is zeroed in G54 mode and tool #0 is selected the user is actually setting the basic offset from G53 Z0. When any other tool number is selected and zeroed when in the G53 coordinate system an offset from G53 Z0 is created. If the control is in G54 or G55 mode the offset is a product of the "base" G54 or G55 offset and the current Z axis display. Individually setting the tool lengths in G55 should not be required, rather the CNC should set a base offset for G55 Z0 from G53 Z0 when tool #0 is selected (and G55 is in effect). When other tool numbers are selected an offset from the G53 to G54 planes PLUS that tools G54 offset will be loaded into the display. To repeat: The G54 and G55 base Z planes are set with tool #0, tool offsets are normally loaded with the control in the G54 mode. Note: switching between G53, G54 and G55 can only be done in Auto or MDI mode. Note: the G-code standard provides for six work coordinate systems, G54 through G59, but to save complexity Stepster only used two. 3 The Linear Move For an example I will use G1 X1 Y.1 Z.01 F10 For simplicity I will specify that all three axis have a step distance of .001 per step. Currently the "move setup" consists of examining each of the three axis for which one has the most steps to move: this will be the total times through the "move loop". Each of the other axis will move proportionately to the longest. So X moves .001 for every iteration through the move loop, Y moves .0001 per iteration, and Z moves .00001 step per iteration. In the move loop the Actual Positions are compared to the Calculated Positions, the differences are the Errors. (this is the definition of ANY servo loop) When the move loop begins the actual position is zero (incremental). The calculated positions are based on the count of the count of cycles through the move loop (iterations), so for the first iteration this is 1. The calculated positions for the example are now X= .001, Y= .0001, Z=.00001. The errors also equal X=.001, Y=.0001, Z=.00001. The rule for stepper motors is "If the error is greater than or equal to one step then command a step and then update the axis' position" so X moves one step (.001), and it now has an error of 0, but Y still has an error of .0001 and Z error still equals .00001. On the second iteration the calculated positions are X=.002, Y.0002, Z=.00002. There is enough error for X to make another step, but not Y or Z. On the tenth iteration through the move loop the Y error finally equals 1 step (.001), so a step for Y is commanded and Y's actual (G53 machine) position is updated and the error reset to zero. On the 100th iteration Z finally takes a step, etc. In a "real" servo cycle CNC the process is similar, but not the same. In a "real" servo cycle the Actual position is read from scales or rotary encoders, in a stepper CNC the actual position is based on the number of step commands previously issued since the axis was zeroed (it works, just be sure you've got a LOT more motor than you think you need.) In my CNC the Calculated position is based on the iteration count, in a "real" CNC it is based on TIME via a clock pulse. (My program sets the feedrate by creating pauses at the end of the move loop with a "delay loop" that is based on a rather complex and inexact formula to set the number of iterations through the delay loop.) In a "real" servo cycle the output is analog- if the "error" is small a weak (slow) signal is sent to the motor. If the error is large the signal is strong ("Hey! Your way off! Hurry up!!"). Notice that to work ALL CNC's must have an error, the tool position is NEVER exactly what it should be (but it can be sooooo close.....) Acceleration and Deceleration Stepper motors will "go out of synch" if they are accelerated or decelerated too suddenly. This is because the pattern of the magnetic fields is advancing faster than the rotor can keep up. Once a motor has been out of synchronization a stepper CNC is useless until it has been re-zeroed, as the position of that axis has been lost. To help prevent this the move loop contains additional delay loops to accelerate and decelerate the motor. The base distances to accel and decel are read from a set up file, naturally they differ greatly from machine to machine. The base distance for accel and decel is modified by the feedrate, the faster the feedrate the grater the accel / decel distance. There is a second setup variable for accel / decel that relates to the computers speed (since this affects how fast the accel / decel loops run), when this is set wrong the accel / decel loops will either have a sharp change of speed at the beginning of accel /decel or at the end of accel / decel. For Stepster this parameter is adjusted by listening to the motors during jogging moves. Backlash Compensation To compensate for play in the leadscrews and additional move for any motor that is reversing direction is commanded, this backlash move happens before the regular move and does not affect the display or Actual position (G53) of the machine. In order for backlash compensation to work on a stepper motor CNC it must work perfectly, any errors will accumulate. One must understand that a user's G-code files may run to tens of thousands of lines of code. In Stepster the backlash compensation is integrated into the regular linear move routine, a variable is set that tells the linear move routine not to update the display and use rapid speed. This is not the best, as it complicates the linear move routine and every line of code here slows top speed. A separate move loop should be used for backlash compensation. FEED PER REV AND THREADING (Not implemented in Stepster.) When a CNC lathe is in the Feed-Per-Rev mode the spindle encoder pulse train is substituted for the TIME pulse. When threading the concept is the same, but the beginning of the move is delayed until the marker pulse (one per rev) from the encoder is read. Let me back up to the requirement: slave the Z axis (Z only, nothing fancy here) to a spindle encoder at a ratio set by the feed command (for convenience the user will enter the exact feed per rev, F.0005 would feed 5/1000 inch per rev). The theory I have is to treat the Z axis like one of the shorter axis: the calculated position will increase by LESS than one step each time through the loop. The loop will "hang" until the pulse is received from the port (like it hangs now till the delay loop is complete), then on to the next iteration. This differs from a normal one axis move in that normally the one axis will always move exactly one step per iteration, not a partial step. Example: I may want to move the Z axis .000023 per pulse, the first time through the loop the Z calculated position would be .000023, no where near the error required to make a step. After enough times through the loop a step would be made, and eventually the move loop would end, based on the actual position equaling the end position. A "rigid tapping" routine would work as above, but require program control of the spindle motor, to reverse rotation of the tap at the bottom, and also must read the spindle encoder for direction. The routine works like this: The G-code block includes Z depth, F feed per rev, and R clearance plane information. The routine first turns the spindle clockwise and begins the z axis feed per rev move. When the Z axis depth is reached the routine commands the spindle to reverse rotation. The Z axis direction does not reverse until the encoder indicates that rotation has reversed. After the spindle has reversed direction and the Z axis has raised up to the R clearance plane the encoder following is ended and the spindle resumes CW rotation. Improved Feedrate Control As mentioned earlier, "real" CNC's use an externally generated clock pulse to regulate motion. This could be simulated for a PC CNC by having a multitasking program flip a bit back and forth. The "move loop" would hang until the bit was set, then continue to the next iteration. The clock signal could be constant if the longest axis was set to LESS than one step per iteration and the other axis were scaled proportionately. For instance, if the longest axis was scaled .01 step per iteration the move would take 10 times as long to finish. It would be best if the "rate clock" could be controlled directly, both to enable true "inch per minute" F codes and to enable the feedrate override by manipulating the rate clock. On a true CNC the rate clock is a separate circuit, on a PC CNC this will probably require a multitasking TSR. One possible option is to have an externally generated clock signal generated on a separate circuit board and input through the LPT port. A potentiometer on this simple 555 timer chip circuit would act as a fully tunable feed override. This would be complex and expensive but would solve all problems in achieving true inch per minute feed rates. 4 The Parser The parser is a section of the program that reads lines of text (from the user's text file or from Manual Data Input) and converts them to commands for the NC to execute. Stepster recognizes N, G, X, Y, Z, I, J, K, L, R, M, S, and T, but no actions for N and S are implemented. There are actually 9 classes of G-codes, and one from each class may be active at the same time. G0 through G9 is the motion class, only G0, G1, G2, G3 and G4 are commonly supported. G10 through G19 are plane class, usually G17, G18 and G19 (arcs in XY, XZ, and YZ planes) are supported. G20 through G29 are ???? G30 through G39 are for feed per rev, G33 is clockwise threading. G40 through G49 are for offsets, G40, G41 and G42 are cutter comp, G43 loads Z axis (tool) offsets, etc. G50 through G59 are for coordinate systems, G51 rotation, G5 scaling, G53 through G59 for coordinate system selection. G60 through G69 ???? G70 through G79 are for repetitive moves, such as bolt hole circles, etc. G80 through G89 are for canned cycles, such as hole drilling and boring. G90 through G99 are for move types, G90 absolute, G91 incremental, G90.1 absolute with absolute I and J, etc. Note: All CNC's use slightly different "flavors" of G-codes, and the G70 and G80 class are notoriously different from one machine to the next. Note: The use of decimal numbers, such as G90.1, is a new innovation to increase the number of possible G-codes. The reason it is important to support the different classes of G-code is so the user can enter complex lines such as: N10 G90 G17 G40 G50 G70 G80 G0 X0 Y0 Z0 The above line would: set absolute moves, arcs in the normal XY plane, cancel cutter comp, cancel scaling and rotation, cancel all canned cycles, and rapid to X0 Y0 Z0. Experienced G-code programmers are taught to use a line like this after every tool change, so the program can safely be started in the middle. Stepster did not properly implement the different classes of G-codes, the user was warned to use separate lines for each G-code. While the complex line in the example above is not mandatory, lines like G54 G92 X0 Y0 Z0 are quite common. Stepster does parse this line correctly, but this is mostly accidental. How the parser works The parser reads one letter at a time. When a recognized letter (N, G, X, etc) is encountered a variable is set to mark the place in an array for the value to be stored. In Stepster the array G(20) is used, G(1) for N, G(2) for the G value, G(3) for X, etc. Notice this does not provide for the different classes of G-code. Once the place in the array is determined the VAL command is applied to the rest of the line of text. In BASIC the VAL command will get the numerical value of an alpha- numeric string. The VAL command will get the proper result even if the string contains a letter. In the following example a$ = "1234abcd" result = VAL(a$) result will be 1234, no error will occur. The parser continues one letter at a time until the entire line is read. After each letter value is read execution branches to blocks of code for each letter (nothing is done to N or S letter values). For X,Y, Z, I, J, and K the read in values are converted to incremental distances (if the control is in the G90 absolute mode) since this is what the move loops require. M codes are executed immediately as read. If the letter read was G control is routed to a series of IF statements. Some G codes are processed immediately, such as G17, G18, and G19. G53, G54 and G55 can also be processed immediately. Since these G-codes are processed as read another G-code on the same line is OK, but this is not as good as storing G-codes in a separate array. Assigning G- codes to the proper class could be done here. Each G code results in a "mode" being set. This "mode" will be used by the post-parser to branch to the appropriate block, linear, circular, dwell and null. Null is provided for when no further action is required after reading the line, such as when a line contains just a G-code that was executed immediately. Stepster can set tool offsets from within the program with the line N10 /T2 L1.23 and calls that tool offset with the line N20 T2 This is NOT the standard way input a tool offset, it was copied from the 1978 Bridgeport Operating System Software (BOSS) versions 4, 5, and 6. Calling a tool length is sometimes done with the T letter, but is more often done with the G43 command N100 G43 T2 or N100 G43 L1.234 Use of the G43 command would probably be quite a bit simpler than the code required to make the / work, it is confusing to use the T letter both to load AND call a tool offset. The Post Parser The post parser controls where program execution branches to. It also provides a place to wait for the user to press the start button when single block operation is selected. In theory at least one branch for program operation should be provided for most of the classes of G-code, at least to all the classes that read X, Y, or Z. G0 and G1 branch to the linear move routine, G2 and G3 branch to the circular move routine, G4 branches to the Dwell routine, The G40 class needs to branch to a routine to set offsets. The G90 class should also branch to a special section, as G92 optionally reads X, Y, and Z. Rotation The post parser is also where rotation can be implemented, this must happen before the move is called. Each X, Y point is converted to a range and angle from the center of rotation (polar coordinates), then the amount of rotation (angle) is added to the bearing and the result is converted back to X and Y (Cartesian coordinates). Scaling Stepster accomplishes scaling by dynamically changing the step distances for the X and Y motors. If the true step distance is .001 and the control is told the step distance is .002 the resultant part will be half size. This approach means that ellipses will be described if X and Y are scaled differently, but unfortunately the Display does not show the scaled value, it shows the original value. Also, the user must return to the exact point where scaling was started or the display will not be correct. An alternate approach would be to insert the code to scale the commanded distances to move just before the post processor calls the move routine. This would result in a correct display and the ability to change / cancel scaling anywhere, but ellipses are not possible. There is no reason why BOTH types of scaling can't be implemented, with G51 and G51.1. 5 Manual Data Input In the MDI mode the user types a block of G-code to be executed. The format of text is the same as for Automatic operation from a text file. After the user presses the Enter key control switches to the parser, then to the post parser, then to the appropriate move routine (if any). It would be best if a variable in the set up file specified whether action is to begin immediately after the user presses return, or whether control should wait for the Start button (or key) to be pressed, real CNC's do it both ways, and some allow a choice. 6 Circular Moves Early CNC's (actually NC's) were only capable of arcs of less than ninety degrees, and they could not cross the quadrant boundaries at 3, 6, 9, and 12 o'clock. This is a much simpler move than a multi-quadrant move where the motors must change direction. Backlash compensation is an added complexity, and the ability to do arcs in the XY, XZ, or YZ planes further complicates the arc routine. The description of the arc routine will start with single quadrant moves, then add the theory for multi-quadrant operation, then backlash, then plane selection. Until the section on planes all examples will be in the XY plane. Single Quadrant Arcs Arcs also work to the servo theory, the actual (present) position is compared to the calculated position (based on time or number of times through the iteration) to give an Error. When the Error is large enough a step is commanded and the actual position is updated. The setup for a single quadrant arc is to find: 1. the Starting Angle This is based on the incremental distance (in X and Y) to the center point of the arc (specified with I and J). The Arc Tangent function is used to return the angle. Note: most computer languages work in radians. Radians are capable of describing arcs to 180 degrees. Several lines of code are required to examine whether the X value of the starting point is greater or less than the X value of the center point and whether the Y value of the center point is greater or less than the Y value of the center point in order to find whether or not to add 180 degrees to the start angle. This code is also required for the Ending angle and is slightly different for clockwise and counter clockwise moves. 2. the Ending Angle This is based on the incremental distance (in X and Y) from the center point to the end point of the arc. These distances are called I1 and J1. The Arc Tangent function is used to return the angle. Note: 360 degrees is added to the Starting or Ending angle so that the Working angle will not cross zero. This is to prevent situations such as when a clockwise arc is started at 10 degrees and ran to 350 degrees. Instead the arc starts at 370 degrees and runs to 350 degrees. 3. the Radius of the Arc Based on the square root of (I squared + J squared), it should also be calculated from I1 and J1, this provides a check of the user's data, the two radii should agree within a small error (say .0015 max), and a warning should occur. If no warning is given the CNC should move in a predictable way even when bad data is input. 4. The Delta, or amount to increment the Working angle for each iteration. This is the amount of change (from zero degrees) at the radius specified for one motor step. The formula used is Arc Tangent (step distance / radius). The larger the radius and the finer the motor step the finer an angle this will be. In Stepster this value was arbitrarily halved to ensure that the Error would never be more than one motor step, this is probably not required, but since the Arc routine never needs to move at Rapid speeds there is no speed penalty. 5. The motor's direction. This is also found by examining whether the arc is above / below and left / right of the center point. The Arc Move Loop The concept of the arc move is : At the beginning of the move the Actual X and Actual Y positions are stipulated by the Sine and Cosine of the Starting angle, and the Working angle is set to equal the Starting angle. X and Y are calculated with Sine and Cosine based on the Working angle. The Working angle is incremented by a set amount smaller than the Arc Tangent of one motor step. The arc ends when the Working angle equals the Ending angle. The arc move loop at it's core is not much different than the linear move loop. The X and Y Calculated positions are found with sine and cosine of the radius at the working angle. These are compared to the Actual Positions and the Error found. When an axis' Error is greater than one motor step a step is commanded and the axis' Actual Position is updated. At the end of the loop the Working angle is incremented by Delta (which is a negative number for clockwise arcs) and the loop repeats until the Working angle equals the Ending angle. Multi-quadrant Operation In order to enable multi quadrant operation I used many blocks of code that examine the working angle and direction of the arc. The first of these "quadrant detection" blocks is at the end of the arc setup. If the working angle is out of range of the first quadrant detection block control drops to the next etc. Eventually the working angle is within the range (quadrant) covered by a block. The motors' direction are then set, and control goes to the move loop for one iteration, then returns to the same block. This continues until the working angle no longer falls in the range (quadrant) controlled by the block, and control drops to the next quadrant detection block, etc. etc. In the move loop is a line examining for the end of the arc (Working angle equals End angle), this sets a value completely out of range so that control will fall through all remaining quadrant detection blocks. In Stepster this added a couple pages of code, it is not an especially pretty example of code, and near duplicate code must be used for clockwise and counter clockwise arcs. If someone can find a more elegant solution I'd sure like to see it. Backlash Compensation All those quadrant direction selection sections ; ) are a fine place to add backlash comp, every time a motor changes direction a few extra steps are added as a linear move. Keeping track of the last direction the motor moved is the toughest part. Three axis Circular Interpolation This allows a linear move for the Z axis as the X and Y axis' describe an arc. To make this work the arc setup must find the total number of iterations based on the difference between the Starting and Ending angles divided by Delta. Then the (incremental) linear distance for the Z axis is divided by this number. The result is the distance the Z axis should move each iteration. Code is added to the arc move loop that compares the Actual position of Z to the Calculated position (based on the number of times though the loop times the Z distance per iteration to yield the Z axis Error, when the error equals or (slightly) exceeds a motor step a step is commanded, just like in the normal move routine. XY, XZ, YZ Plane Selection This greatly adds to the complexity of the arc move loop. Instead of X, Y, and Z the arc routine must work with the Cosine axis, the Sine axis, and the Linear axis. At the beginning of the routine X, Y, and Z are assigned to the proper variables, then during the loop they must be re- assigned to X, Y, and Z to update the display. Fortunately, even though this complexity slows the arc routine significantly arcs are never done at maximum feedrate. 7 Automatic Operation Automatic operation is simply MDI with file manipulation added. There is some thought to reading an entire users file and putting it into a matrix in memory to gain some speed. I don't feel this is required, the time required for a P-75 with a slow 80 meg hard drive to parse a line of text is a few milliseconds, it is impossible to detect with the eye. A line of text is only parsed once, a move loop may iterate tens of thousands of times. Also, a user's file may be tens of thousands of lines long, allocating this much variable space may be difficult. 8 Canned Cycles Canned cycles are inserted into the post parser, in this case they come AFTER the move and before control is returned to get the next line of text to parse. Canned cycles contain instructions for at least one additional move, so new values to move to are set, then another move is commanded by switching control directly to the move routine so that another move can be executed. After the homing moves the "mode" is set to null and control goes to the top of the post parser, this is done because the post parser also contains a duplicate of the code to update the display. Note: Neither the code for rotation or canned cycles should be in the post parser, just IF statements that cause GOSUBS to the appropriate subroutines.