The purpose of the G2002 is to get around some shortcomings of PC based "software only" solutions for motion control applications. By making it open source, this can be an inexpensive, customizable and high performance alternative.
My thinking is there are 3 ways CNC programs are implemented presently:
(1) Software only, DOS based. Inexpensive and adequate pulse rates (100 kHz). Funky looking DOS screen, antiquated OS drivers and support for anything else the PC has running.
(2) Software only, WinXX? based. Cool looking GUI. Miserable pulse rates (<10 kHz).
(3) Software / hardware. WinXX? based. Expensive to very expensive. The hardware is not properly done on the low end versions.
The G2002 would off-load the step pulse generator from software timing loops to hardware specifically designed for that purpose.
Here's how the G2002 works: (1) A novel method of generating pulse frequencies and (2) digitally integration of velocity with time to calculate position. I will handle each seperately, then bring them together at the end. I will also describe breifly in (3) how a practical axis move is made.
(1) You are probably familiar with the usual method of generating frequencies; load a value into a counter, have it count down to zero, output a pulse, re-load the value an so on. As an example, say you load a down-counter (decimal) with "9" and clock it at 1 MHz. The counter would count down 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, output a pulse on the "0" count, re-load with "9", count down again, and so on. The frequency would be 10 counts at 1 uS each for a frequency of 100 kHz.
The next higher frequency at your disposal would be to load the counter with "8". This would result in a period of 9 uS (8, 7, 6, 5, 4, 3, 2, 1, 0) and a frequency of 111 kHz. Your frequency would have increased 11 kHz.
Now let's look at the top end. The highest frequency would be to load the counter with "0". You would then have a frequency of 1 MHz (an output pulse for every clock cycle). Now how about the next highest frequency, which would be to load the counter with a "1". The sequence now would be 1, 0, 1,0 etc. The period of course be 2 uS for a frequency of 500 kHz. That is a frequency change of 500 kHz.
So what is wrong with this picture? Well, at the low end, an increment of period broduces an 11 kHz change while at the high end the same increment of change results in a 500 kHz change! This is non- linear (1/X) and terrible!
Now let's try it a different way.
Let's take an adder (single digit, decimal). It has two inputs (A) and (B) , and two outputs, (A+B) and carry. Take the output (A+B) to the input of a clocked storage element that stores the (A+B) output on every clock pulse (still 1 uS). The output of the storage element feeds back to the adder's (B) input. Now let's see what happens on each clock pulse if the (A) input is "1" and the storage element initially outs a "0". NC will mean "no carry" while the output pulse will occur on "carry", or C.
1+0=1 NC, 1+1=2 NC, 2+1=3 NC, ... 8+1=9 NC, 9+1=0 and a C, 1+0=1 NC, 1+1=2 NC, etc.
There will be a "C" every tenth clock pulse for a frequency of 100 kHz.
Now make the value on (A) equal to 2 (next highest frequency) and see what happens.
2+0=2 NC, 2+2=4 NC, 4+2=6 NC, 6+2=8 NC, 8+2=0 C, 0+2=2 NC, etc.
There will be a "carry" every 5 clock cycles for a frequency of 200 kHz, or a 100 kHz change. Now how about what happens at the high frequency end? The biggest number we have available is "9", so let's use it.
9+0=9 NC, 9+9=8 C, 9+8=7 C. .... 9+2=1 C, 9+1=0 C, 9+0=9 NC, 9+9=8 C, etc.
What we have now is 9 output pulses for every clock cycles for a frequency of 9/10 of 1 MHz, or 900 kHz.
If we replace (A) with "8", we will get 8 "carrys" for every 10 clock cycles for a frequency of 800 kHz. Well, this is much better; we have an increment of frequency change produce an equal change (100 kHz) in frequency. Now the relationship between frequency command ( A input) and frequency is linear, i.e. the change in frequency per command increment is independent of frequency range.
There is a little problem though. Look at th 900 kHz example above. There is a 2:1 change in period every 9th ouput pulse, because there is a "missing " pulse every 10th clock cycle. This is not good.
Here's what can be done about it. We will utilize some natural advantages at our disposal. The adder/storage circuit is implemented in hardware, which means rather than using 1 MHz clock mentioned in the examples, say we use a more practical value of 25MHz. The hardware can easily handle that (74HC series logic tops out at 80 to 100 MHz).
The maximum pulse rate then would be 25 MHz and would have to be divided down to a usable value. The pulse period is 40 nS and 80 nS for the "missing" pulse period before division. If the divider is set to 1,000 then the the pulse frequency would be 250 kHz with 40 nS of phase "jitter" (80 nS - 40 nS) contributed by the "missing pulse" period.
This now is a pretty pure frequency spectrally, having less than 1% variation in period pulse-to-pulse (40 nS / 4 uS). It would be less then 0.1% at 25 kHz and so on. This is very good.
Obviously the adder/storage element is not a single decimal digit, it is actually a 16-bit binary one. This provides 65,536 (2 to the 16th power) evenly spaced frequencies. The divider is tapped, making the frequencies suitable for full-step, half-step, 10 microstep and 125 microstep drives.
(2) Motion Control requires moving a known distance at a known speed. We have a method of generating known speeds (step pulse frequencies) but how do we know the distance moved?
The obvious method would be to count the step pulses to a "position" counter. At 25 MHz this would require a hardware counter and a means for the computer to read its contents. All this is unnecessary if a different approach is taken.
From integral calculus (or high-school physics) we know that distance is the first integral of velocity with time. For integration of velocity with time to be succesful we must know the velocity and the time perfectly, otherwise even the most miniscule error in either will produce an unacceptable error in position. This not difficult to do if we take advantage of a trick.
The 25 MHz timebase generates the step pulses (velocity). We will use the same timebase to generate time periods as well by dividing it by a fixed amount.
Let's say we divide the 25 MHz by 25,000. This will result in a 1 millisecond period. Let's also command a 1 kHz step pulse frequency. From this we can assume the distance taken every 1 millisecond time will be 1 step (1 millisecond times 1 kHz = 1). All we have to do now is to sum the velocity to a position register every 1 millisecond time tick to know our position!
What would happen if the timebase changed? Let's say our 25 MHz oscillator dropped to 12.5 MHz for some reason. The 1 millisecond time tick would now strech out to 2 milliseconds and our step pulse frequency would drop to 500 Hz as a result. The distance moved per time tick would remain unchanged (2 milliseconds times 500 Hz = 1). It balances out perfectly.
(3) Let's now see how a practical move might be made. For this we need some parameters on which to base our move. They are:
ACC Rate of acceleration. It is a fixed value. Let's it is "2" VEL The commanded velocity. This number changes as things move along. It is "0" initially. MAX The target velocity. This may be the feed-rate. It is fixed value. Let's say it is "10" DN The distance of the move. This value will change from the initial value. Make it "120" UP The distance moved. The initial value is "0". It will change as things move along.
Here's how it works:
1. Add ACC to VEL 2. Is VEL bigger than MAX ? 3. Make VEL = to MAX if yes to question in step 2 4. Subtract VEL from DN 5. Is VEL = to MAX ? 6. Add VEL to UP if no to question in step 5 7. Is UP bigger than DN ? 8. Go to step 1 if no to question in step 7 9. Subtract ACC from VEL 10. Is VEL zero? 11. Go to step 9 if no to question in step 10 12. Done
Here is what VEL, DN and UP would look like on each time tick.
TIME VEL DN UP
0 0 120 0 Ready to move 1 2 118 2 VEL becomes VEL + ACC, accelerating 2 4 114 6 VEL becomes VEL + ACC, accelerating 3 6 108 12 VEL becomes VEL + ACC, accelerating 4 8 100 20 VEL becomes VEL + ACC, accelerating 5 10 90 30 VEL = MAX, end acceleration 6 10 80 30 Run at a constant speed (MAX) 7 10 70 30 Run at a constant speed (MAX) 8 10 60 30 Run at a constant speed (MAX) 9 10 50 30 Run at a constant speed (MAX) 10 10 40 30 Run at a constant speed (MAX) 11 10 30 30 Run at a constant speed (MAX) 12 10 20 30 UP > DN, begin deceleration 13 8 12 30 VEL becomes VEL - ACC, decelerating 14 6 6 30 VEL becomes VEL - ACC, decelerating 15 4 2 30 VEL becomes VEL - ACC, decelerating 16 2 0 30 VEL becomes VEL - ACC, decelerating 17 0 0 30 Done, move finished, axis stopped
The move took 17 time "ticks" to move the required 120 steps. It accelerated to the programmed velocity of 10, decelerated and came to a stop at the 120th step pulse.
The algorithm listed above is obviousy more complicated than that. There are other decisions it has to make for things work out, just change the value of DN to 123 to see why. These other decisions are not shown because it would confuse the understanding of the underlying principal of how a move is made.
If DN, ACC, VEL, etc is treated as a vector, then the output velocity must be multiplied by SIN and COS for that vector's angle before they are output to the individual X, Y axies step pulse generator cards. For linear interpolation moves SIN and COS multipliers are constant and may be pre-calculated.
If it is a circular interpolation move, then SIN and COS are variables and must be calculated on every time tick. The algorithm then generates 1,000 polygon sides per second. This means if you are moving at 1" per second around a 1" circumfrence (0.159" radius circle), then the resulting path will be a 1,000 sided polygon. If the radius were 1.59" and the speed were 0.1" per second, then the "circle" would have 100,000 sides and so on.
The other thing to note is the algorithm relies on simple add, subtract and branch instructions that execute very rapidly. A usable version takes about 400 clock cycles to execute, which takes only 16 uS for a 25 MHz clock rate. From that you can see many axis can be serviced within a 1,000 uS time tick as an interrupt service routine.
Circular interpolation places the greatest computational burden on the microprocessor because a SIN and COS pair has to be calculated on each interrupt cycle. This takes about 2,400 clock cycles, so let's say the entire algorithm takes 4,000 clock cycles per axies- pair as a very conservative time estimate. The execution time would then be 160 uS. Running circular interpolation simultaneously on 6 axies (???) would take 480 uS, which means the interrupt service would at most occupy 50% of the processor's time. That leaves plenty of time for the background program to communicate with a PC, massage data and feed the velocity manager.