CNC - I May Not Be Crazy After All.... Multi-Axis Stage Synchronization
idbruce
Posts: 6,197
This thread is a follow up thread to this previous thread: forums.parallax.com/discussion/166320/cnc-i-may-be-crazy-but
I am starting a new thread for a fresh start, with fresh discussion, to bypass all the jibber jabber in the other thread. So please forgive me.
Anyhow, the previous thread referred to an analogy of using gears to simulate synchronized motion. After thinking about this and studying this for a good portion of the morning, I have come to the conclusion that the gear analogy can be applied to the Propeller for synchronization amongst several axes. I now believe that the key to implementing this analogy is the dreaded floating point support and being able to do comparisons with floating points.
However, I will most likely need a little help from some members to actually prove my theory.
When it comes to the gear analogy, as applied to stepper driven axes, think of the required steps for each axis, as the teeth of individual gears. For example, consider the following step requirements to complete a G0 or G1 movement:
X Axis 137 steps
Y Axis 83 steps
Z Axis 19 steps
137 / 83 = 1.650602409638554 ratio
137 / 19 = 7.210526315789474 ratio
For the sake of discussion and for a quick source to cut and paste from, here is some code that I wrote about a year ago, for synchronizing two axes.
From this code, hopefully you can see the attempt at the synchronization. Anyhow, lets examine a small portion of this code:
This small section of code actually works quite well for being written in Spin, however for what I will be proposing, I believe that Spin will be far to slow, but I will be discussing it in Spin, just so that most folks can participate in the discussion.
Okay... So now let's assume that in addition to ramping up steps, running steps, and ramping down steps, I also maintain the current count of steps taken, and let's assume that the axis with the most steps is the master. During each iteration, of the control loop for the master, we will have the current step number. A comparison is made for the individual slave ratios, for the current step number and the next step number. Can multiples of a slave ratio fit between these to steps, and if so, then UPDATE a global variable with a timing offset, so that the slave makes a step at he precise time, according to the ratio of the slave.
So basically, each of the loops, shown immediately above, would do these comparisons, which would all be floating point comparisons.
I believe that by incorporating floating point support into the above loops, we could achieve the gear ratio analogy, which would include ramp ups and ramp downs of various axes.
I am starting a new thread for a fresh start, with fresh discussion, to bypass all the jibber jabber in the other thread. So please forgive me.
Anyhow, the previous thread referred to an analogy of using gears to simulate synchronized motion. After thinking about this and studying this for a good portion of the morning, I have come to the conclusion that the gear analogy can be applied to the Propeller for synchronization amongst several axes. I now believe that the key to implementing this analogy is the dreaded floating point support and being able to do comparisons with floating points.
However, I will most likely need a little help from some members to actually prove my theory.
When it comes to the gear analogy, as applied to stepper driven axes, think of the required steps for each axis, as the teeth of individual gears. For example, consider the following step requirements to complete a G0 or G1 movement:
X Axis 137 steps
Y Axis 83 steps
Z Axis 19 steps
137 / 83 = 1.650602409638554 ratio
137 / 19 = 7.210526315789474 ratio
For the sake of discussion and for a quick source to cut and paste from, here is some code that I wrote about a year ago, for synchronizing two axes.
CON _CLKMODE = XTAL1 + PLL16X _XINFREQ = 5_000_000 'Clock Frequency Equation CLK_FREQ = ((_CLKMODE - XTAL1) >> 6) * _XINFREQ 'X Axis X_DIRECTION = 22 X_STEP = 23 X_DISABLE = 24 X_PULSE_WIDTH = CLK_FREQ / 1_000_000 '= 80 X_MIN_SPEED = CLK_FREQ / 5_714 '= 14_000 Rounded X_MAX_SPEED = CLK_FREQ / 16_000 '= 5_000 X_RAMP_INC_DEC = CLK_FREQ / 26_666_666 '= 3 Rounded X_INC_DIR = 0 '= Counter-clockwise rotation X_DEC_DIR = 1 '= Clockwise rotation X_STEPS_PER_REV = 2_000 '= 200 Full steps * 10 microsteps X_STEPS_PER_METER = 55_555 X_RAMPING_STEPS = (X_MIN_SPEED - X_MAX_SPEED) / X_RAMP_INC_DEC X_TOTAL_RAMPING_STEPS = X_RAMPING_STEPS * 2 X_MAX_STEPS = 16_933 '12 inches of linear travel 'Y Axis Y_DISABLE = 25 Y_STEP = 26 Y_DIRECTION = 27 Y_PULSE_WIDTH = CLK_FREQ / 1_000_000 '= 80 Y_MIN_SPEED = CLK_FREQ / 5_714 '= 14_000 Rounded Y_MAX_SPEED = CLK_FREQ / 16_000 '= 5_000 Y_RAMP_INC_DEC = CLK_FREQ / 26_666_666 '= 3 Rounded Y_INC_DIR = 1 '= Counter-clockwise rotation Y_DEC_DIR = 0 '= Clockwise rotation Y_STEPS_PER_REV = 2_000 '= 200 Full steps * 10 microsteps Y_STEPS_PER_METER = 55_555 Y_RAMPING_STEPS = (Y_MIN_SPEED - Y_MAX_SPEED) / Y_RAMP_INC_DEC Y_TOTAL_RAMPING_STEPS = Y_RAMPING_STEPS * 2 Y_MAX_STEPS = 16_933 '12 inches of linear travel VAR LONG lMoveNow BYTE bX_Direction LONG lX_MoveComplete LONG lX_RampingSteps LONG lX_RunningSteps LONG lX_Stack[100] BYTE bY_Direction LONG lY_MoveComplete LONG lY_RampingSteps LONG lY_RunningSteps LONG lY_Stack[100] PUB Main COGNEW(X_Stage, @lX_Stack) COGNEW(Y_Stage, @lY_Stack) WAITCNT(CNT + (3 * CLKFREQ)) TestStageSyncronization PUB TestStageSyncronization lX_MoveComplete := TRUE lY_MoveComplete := TRUE 'Prepare X stage for positive movement UpdateX_RampingAndRunningSteps(X_MAX_STEPS) bX_Direction := X_INC_DIR 'Prepare Y stage for positive movement UpdateY_RampingAndRunningSteps(Y_MAX_STEPS) bY_Direction := Y_INC_DIR 'Now make the moves lMoveNow := TRUE 'Loop until the moves are complete and then reinitlize the variables REPEAT WHILE lX_MoveComplete == FALSE AND lY_MoveComplete == FALSE lX_MoveComplete := FALSE lY_MoveComplete := FALSE 'Wait a bit for test purposes WAITCNT(CNT + (5 * CLKFREQ)) 'Now get ready to go in the opposite direction 'Prepare X stage for negative movement UpdateX_RampingAndRunningSteps(X_MAX_STEPS) bX_Direction := X_DEC_DIR 'Prepare Y stage for negative movement UpdateY_RampingAndRunningSteps(Y_MAX_STEPS) bY_Direction := Y_DEC_DIR 'Now make the moves lMoveNow := TRUE PUB UpdateX_RampingAndRunningSteps(TotalSteps) 'If maximum speed can be obtained, determine the actual number of 'ramping and running steps. IF TotalSteps > X_TOTAL_RAMPING_STEPS 'Copy the global amount of ramping steps lX_RampingSteps := X_RAMPING_STEPS 'Determine the number of full speed steps. lX_RunningSteps := TotalSteps - X_TOTAL_RAMPING_STEPS 'Else ramp upto the half-way point and then ramp down. ELSE lX_RampingSteps := TotalSteps / 2 lX_RunningSteps := TotalSteps // 2 PUB UpdateY_RampingAndRunningSteps(TotalSteps) 'If maximum speed can be obtained, determine the actual number of 'ramping and running steps. IF TotalSteps > Y_TOTAL_RAMPING_STEPS 'Copy the global amount of ramping steps lY_RampingSteps := Y_RAMPING_STEPS 'Determine the number of full speed steps. lY_RunningSteps := TotalSteps - Y_TOTAL_RAMPING_STEPS 'Else ramp upto the half-way point and then ramp down. ELSE lY_RampingSteps := TotalSteps / 2 lY_RunningSteps := TotalSteps // 2 PUB X_Stage | Counter, StartStopSpeed DIRA[X_DIRECTION] := 1 REPEAT IF lMoveNow == TRUE lMoveNow := FALSE StartStopSpeed := X_MIN_SPEED 'Set up the CTRMODE of Counter A for NCO/PWM single-ended. CTRA[30..26] := %00100 'Set the output pin for Counter A. CTRA[5..0] := X_STEP 'Set the value to be added to PHSA with every clock cycle. FRQA := 1 'Set APIN as an output. DIRA[X_STEP] := 1 'Set the OUTA register to match the desired direction of rotation. OUTA[X_DIRECTION] := bX_Direction 'Get the current System Counter value. Counter := CNT 'Ramp up the stepper motor to maximum speed. REPEAT lX_RampingSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -X_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed -= X_RAMP_INC_DEC) 'Maintain maximum speed REPEAT lX_RunningSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -X_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed) 'Ramp down the stepper motor and come to a stop. REPEAT lX_RampingSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -X_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed += X_RAMP_INC_DEC) lX_MoveComplete := TRUE PUB Y_Stage | Counter, StartStopSpeed DIRA[Y_DIRECTION] := 1 REPEAT IF lMoveNow == TRUE lMoveNow := FALSE StartStopSpeed := Y_MIN_SPEED 'Set up the CTRMODE of Counter A for NCO/PWM single-ended. CTRA[30..26] := %00100 'Set the output pin for Counter A. CTRA[5..0] := Y_STEP 'Set the value to be added to PHSA with every clock cycle. FRQA := 1 'Set APIN as an output. DIRA[Y_STEP] := 1 'Set the OUTA register to match the desired direction of rotation. OUTA[Y_DIRECTION] := bY_Direction 'Get the current System Counter value. Counter := CNT 'Ramp up the stepper motor to maximum speed. REPEAT lY_RampingSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -Y_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed -= Y_RAMP_INC_DEC) 'Maintain maximum speed REPEAT lY_RunningSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -Y_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed) 'Ramp down the stepper motor and come to a stop. REPEAT lY_RampingSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -Y_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed += Y_RAMP_INC_DEC) lY_MoveComplete := TRUE
From this code, hopefully you can see the attempt at the synchronization. Anyhow, lets examine a small portion of this code:
'Ramp up the stepper motor to maximum speed. REPEAT lY_RampingSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -Y_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed -= Y_RAMP_INC_DEC) 'Maintain maximum speed REPEAT lY_RunningSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -Y_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed) 'Ramp down the stepper motor and come to a stop. REPEAT lY_RampingSteps 'Send out a high pulse on the step pin for the desired duration. PHSA := -Y_PULSE_WIDTH 'Wait for a specified period of time before sending another 'high pulse to the step pin. WAITCNT(Counter += StartStopSpeed += Y_RAMP_INC_DEC) lY_MoveComplete := TRUE
This small section of code actually works quite well for being written in Spin, however for what I will be proposing, I believe that Spin will be far to slow, but I will be discussing it in Spin, just so that most folks can participate in the discussion.
Okay... So now let's assume that in addition to ramping up steps, running steps, and ramping down steps, I also maintain the current count of steps taken, and let's assume that the axis with the most steps is the master. During each iteration, of the control loop for the master, we will have the current step number. A comparison is made for the individual slave ratios, for the current step number and the next step number. Can multiples of a slave ratio fit between these to steps, and if so, then UPDATE a global variable with a timing offset, so that the slave makes a step at he precise time, according to the ratio of the slave.
So basically, each of the loops, shown immediately above, would do these comparisons, which would all be floating point comparisons.
I believe that by incorporating floating point support into the above loops, we could achieve the gear ratio analogy, which would include ramp ups and ramp downs of various axes.
Comments
With 32-bit longs one could use Fix-Point integer to do the math, on the end you need integer as result anyways. What Bresenham does (your analogy with gears) is to synchronize two (or more) axis by letting the axis with less movement wait longer between steps so that both(all) axis start to move at the same time and end their moves at the same time.
When using one Cog per axis we can use CNT to sync since all Cogs have the same cnt value.
So for your given numbers-ratios one could say
wait 100 cnt counts between each step of the x-axis
wait 165 cnt counts between each step of the y-axis
wait 721 cnt counts between each step of the z-axis.
Now we need to include the numbers of cnt counts used by one step. This can vary for different axis. Say one axis is a stepper, one a servo, one a linear actuator.
say x-axis needs 10 PASM-Instructions for one step, say 40 cnt counts.
say y-axis needs 20 PASM-Instructions for one step. say 80 cnt-counts.
say z-axis needs 15 PASM-Instructions for one step. say 60 cnt-counts.
so x-axis needs to wait 100-40=60 counts after each step.
and y-axis needs to wait 165-80=85 counts after each step.
and z-axis needs to wait 721-60=661 counts after each step.
you can't lose any steps by rounding errors (like adding float increments to position values will give you).
it takes into account how long a axis need to make the move of one step (and even ask a encoder if step done).
it is easy adaptable to a variety of PASM drivers for different servo/encoder/actuator. And automagically adapts to more then 3 axis.
Just run 6 PASM driver for U,V,W,X,Y,Z
Enjoy!
Mike
The waitcnts are never consistent amongst the axes, due to ramp ups and ramp downs. The master sets the pace, according to it's specific ramp and ramp downs. The ratio is used to determine when the slaves fire based upon the specific pace and step index of the master. Just before the master makes a step, it sets a timer for each of the slave axes, based upon the masters pace and step index, providing it is within the slave ratio parameters for a step time.
Parameter Block would contain a start-cnt, new-position, delay-cnt (between steps)
Main COG gets next G-code calculates delays for each axis, fills parameter blocks and when ready activate commands for all axis by writing a start-cnt value.
Enjoy!
Mike
Exactly, I think we are barking here at the same tree.
to activate a movement, I want the master to write the ratio in the parameter blocks for each axis and then a (future) cnt value into the parameter blocks of all a axis. All COGS will then wait for that cnt value and start their movements syncron. That is the same as your:
Just before the master makes a step, it sets a timer for each of the slave axes, based upon the masters pace and step index, providing it is within the slave ratio parameters for a step time.
But each axis just gets start cnt, new position and - based on ratio - a ideal step-time per step. Ramping would be done by each axis like you are doing it now. This could also be synchronized via a count value, 'ramp up in 200 counts then full speed' or something along this line.
The main goal is that the axis-COG does all the movement for the command, reporting continuously back in the Parameter block if success or error.
The master COG then can issue a movement to all axis-Cogs synchronized, and then can monitor the parameter blocks for actual position and failure-conditions.
Basically what your spin code does, except the axis-cogs do the steps by them self and the master just sends complete movements, not steps.
Because on a multiprocessor COGs can do work at the same time, even if in most Propeller programs the calling COG waits for the result of the called cog, But especially here it makes sense to use the independence of a COG to drive the movement smooth, without having to serialize all steps of all axis to send the movement for each step from a single master COG.
And that is something the P1/P2 can and ARM/PIC/Arduino/... can't.
They have to run it from one single process, serial/sequencial, and resort to interrupts to get stuff done in between, from the same cpu, just interrupting the main serial/sequential process. That's why the TeaCup software is such a mess and not easy to port to the P1.
It has to be done the Propeller (and @chip) way. Then it simplifies itself.
Enjoy!
Mike
As mentioned above, I'm not sure the lowest level code needs to be floating point - sure, you can calculate the fit using floating point, but then those final WAITCNT lines are all using 32b values, not floating point.
Notice in all cases, the Steps, and dT's can be whole numbers.
This keeps floating point away from the fastest code, and also means you have more chance of fitting the move-to code into a COG, in PASM.
Then you have the choice of doing the floating point fits, in a separate slower COG, or upstream in something better able to manage floating point.
That's quite a clean delineation of which code does what.
I still don't think you understand what I am trying to say. Let's look at the step requirements first...
X Axis 137 steps
Y Axis 83 steps
Z Axis 19 steps
137 / 83 = 1.650602409638554 ratio
137 / 19 = 7.210526315789474 ratio
In this scenario, X is the master, with the most steps. So now let's look at the ramp up loop for the master.
And now a modified loop
In my last post, pertaining to the modified loop, floating point support would be necessary for comparisons, but most importantly these calculations:
I believe the portion of StartStopSpeed which would be used to set the timer offsets would be calculated using the fractional portion of the individual ratios
perhaps if we work backwards in WAITCNT units, this will be clearer
If I use your numbers :
X Axis 137 steps
Y Axis 83 steps
Z Axis 19 steps
137 / 83 = 1.650602409638554 ratio
137 / 19 = 7.210526315789474 ratio
Suppose that X axis has a step dT of 10,000 SysCLKS at 80MHz, for a step rate of 125us, and a path transit time here, of 17.125ms
The ideal Y dT becomes 16506.02409 SysCLKS, and Z dT is 72105.26315
The INT-rounded Transit times are 83*16506/80M = 17.124975ms and 19*72105/80M = 17.1249375ms, & the latter case finished 62.5ns early.
If that 62.5ns is a problem, you could use a fixed-point rate-multiplier type fractional dither, and here a 1 in 4 == 0.25 fraction, would finish within 3.125ns, or well under 1 SysCLK.
Notice how the WAITCNT values here are all whole numbers, and if you wanted to preserve precision over many whole numbers, there are a couple of choices - you can calculate and send each dT, or you can use an approximation via fixed-point rate multiplier.
In the rate-multiplier case, you dynamically set dT to a choice of either 72105, or 72106, and [72105,72105,72105,72106] averages to 72105.25
I only managed to grasp a small portion of what you were trying to tell me. Is there any possibility I could talk you into providing a pseudo code example? Perhaps that will help me to get a better hold of this.
The key point, was the WAITCNTs are all integer, or possibly fractional fixed-point. No floating point is needed at this layer.
I'd suggest starting without complex acceleration profiles at the lowest levels, then see if there is code space to add that.
You work in the time-domain, and make the transit-times match, for all relevant axes.
There is some cost to adding bits to a rate multiplier fraction, so I'd work backwards - if we take 80M and let's say 26 bits of time, that allows 12.5ns LSB to a slowest step rate of
just under 1 second (838.8608ms), that gives 6 bits spare for fixed fractional steps, in a 32bit dT value calculated.
A 6 bit Rate multiplier is 6 IF..ELSIF statements, and runs a 6b counter (Fc), and the 6b Fractional value, and you can average over many delays, to a finer-resolution.
You can do this Bruce, just need to think outside of the box a little bit... I had to write math routines that perform operations as if you were doing them long hand ... 10 Digits of BCD to 24-bit binary and back in longhand.
Probably simpler to use a pin or hub memory location for sysclk to simplify acceleration control.
I honestly don't know how this will all work out, but if I can get the steppers to attain and maintain a decent speed, then I think that there could be benefits to this code, even though I maybe wasting horsepower.
The way I see it, this code has the potential to eliminate a lot of the messy and difficult code associated with CNC operation, such as trajectory planning, lookahead, etc..., because those operations will be done on the fly.
I currently do not have any particular use for this code and it is more just of an experiment to see if I can achieve master and follower operation, using the gearing analogy.
I have linked to this particular IC chip numerous times throughout the forum, but at the risk of being overly redundant here it is again. Chip: novaelec.co.jp/eng/MCX514_e.html
Instead of being applicable to any particular CNC machine or operation, this code is meant to experiment with some similarities between the Propellers abilities and the above linked chip.
you solved a problem for me, I was stumbling about, the ramping.
My plan was to use CNT to sync all axis-COGS. But using the counters and provide a pin-based master frequency would solve all the ramping issues by just varying the frequency of that sysclock-pin. Can't do that with CNT.
Neat.
So back to simple steps without ramping, way more easy for the axis-cog.
I am just throwing together a simple test harness for my plan using one COG each axis. Not that I need a G-Code controller at all, its more some sort of brain-gymnastic to keep my mind off my current job for a while.
Never did much with the counters of P1, need to read up there. But a hub-location would be quite easy to use.
Enjoy!
Mike
Jason is right, in that you do not have to use multiple COGs, but they are there already, and have some benefits on allowing differing drive types (any mix of Step.Dirn and Qa.Qb is possible), and fast fault protection, easier debug, less caveats, etc.
Quick exercise:
a) The simplest, single COG low level code could accept 6 bits of direct port XOR as 3 axis Step.Dirn, and 32-6=26 bits of dT (838ms), as 32b streamed data.
The streamed info can then also embed the Step pulse width, and can locate any edge to SysCLK levels of precision, with some caveats around very close pulses.
b) Multi-COG approach could be broadly similar encoding, but splits the hub mailboxes to a per-axis basis (with larger average dT), and less caveats as adjacent (same COG) pulses are now step-rate spaced (a large number of SysCLKs) and Step pulse width can be locally generated, which lowers bandwidths.
One parameter to watch here, is how fast you can feed coordinates, and I think QuadSPI has benefits over classic Serial.
QuadSPI allows R/W to Serial FLASH, and also to USB-QuadSPI bridge parts with minimal changes.
I may be wrong, because this is way above my head, but it seems you guys (aka as the P1 beyond-the-box brains trust ) seem to have reached a broad consensus methodology on the way forward for P1 CNC control.
Now who can code up a starter demo?
I'm flattered, but not me. I have little or no time for the foreseeable future, and I get the impression that Jason, jmg, and msrobots have far more hands on experience in that area than I do.
It will most likely be Jason, Dave Hein, and myself, that resolve the CNC endeavor, because all three of us have been knee deep into the CNC code, but apparently I am insignificant, because you left my name off the list
Considering my past efforts, I now believe it is best to abandon the Spin ship, because most of my serious efforts have been done in C.
It took me a while to decide what I wanted, needed, and what code I had available for testing my present idea, as well as testing the ideas of others. After altering some of my past code, to make it adaptable to the PBOE, instead of my controller boards, I think I have come pretty close to a test container, that we can use to test various schemes.
I must freely admit that it has been a while since I last messed with this code. However, I do believe this code, or a good portion of it, should be able to open an SD file, and parse G1 commands, and initiate movement. And perhaps I am jumping the gun a bit, but I am tossing it up here for now, just in case someone wants to toy with it.
Tomorrow I will be testing various aspects of it in greater detail.
In it's current state, the build is very close to reaching the memory barrier.
I haven't read the whole thread but I'm not sure if you have the right "scope" of what problems really need to be solved to build a CNC motion controller. The "gear" idea doesn't help much. You need to consider...
* Direction can change during a movement, so assigning a master axis doesn't make much sense. Only G0 movements can be done isolated, e.g. as unique ramp-up, travel, ramp-down sequence. For a circular movement the velocity of each axis can cross zero and change sign while moving.
* Ramp-up and down can span several G1 moves. Or in other words, you really don't want to stop at the end of each G1 move. Ramps and axis synchronisation (trajectory interpolation) are ideally handled separately.
* Bresenham doesn't work well because it generates frequency jitter. Assume the master axis runs at 10kHz and has to do 3000 steps while the slave makes 2000 steps. The slave would end up making a series of step and no-step cycles resulting in a frequency range of 5 to 10kHz. Pixels on a graphic screen don't have inertia, but mechanics do. The motor had to accelerate and decelerate each time to half/double the current speed making it very sensible to stalling. Even with microstepping the available torque drops drastically the more frequency jitter is present.
For all this reasons I would vote for the multi-cog solution. One cog for trajectory planning and ramps and one cog for each axis.
Can you please provide a G-Code example, as well as the type of equipment?
I realize this. A linear movement can also cross zero and change sign while moving.
Such as machining a square or rectangle with rounded corners?
Perhaps this comment and the last may be true for some machining processes, but I do not believe it will hold true for all CNC equipment. For example a drilling CNC machine or a pick and place machine.
If you are using stepper motors, instead of servo motors, how can this be avoided? You will always have one step rate that is higher and one that is lower, unless they are moving equal distances. And once again, unless you are removing material during axis travel, I do not believe this would be a problem.
There are a lot of CNC machines that can be built and based upon starting at zero and stopping at zero
The reason I am posting this code, is because I now have a new concept, pertaining to the gearing concept and the attached, which is as follows. The current program model includes four drivers intended to run in separate cogs, but considering the concept of the gearing scenario, I eventually came to the conclusion that all movement should be contained within one cog. However, the problem then became an issue of master/follower identification, relationship, pin identification, and pin states to establish direction of rotation. While thinking about the gearing concept, I thought to myself, "It sure would be nice if I could set master/follower parameters, within one cog, before initiating movement". This morning, I think I have come up with the answer. I believe the answer is to alter the GCODE_STRUCT to include pin identifications and axis rotation information, as well as the ratios required for synchronization.
Yep... I do believe that this might be the answer to a problem that I have been trying to solve for a very long time and if not, then I guess I will jump on board with Jason and Bressenham
EDIT: Okay, that will not work, considering the following...
These updates will have to take place in other cogs, otherwise they will slow down the master axis and remove any possibility of synchronization.
In the programs current state, a G-Code file is parsed, and a GCODE_STRUCT is filled from various computations of the parsing. This struct is then passed onto the stepper drivers for movement. However this will all be changing very shortly, whereas the GCODE_STRUCT will simply become a stepping stone for the MOVE_STRUCT. Once the GCODE_STRUCT has been filled from parsing, additional computations will be made from this data, and the MOVE_STRUCT will be filled from these additional computations.
The MOVE_STRUCT will eventually replace the GCODE_STRUCT within the stepper drivers.
Nothing is written in stone just yet, but this is the direction I am heading.
EDIT: It is also worth noting that I believe this effort will significantly shrink the current build size and increase the speed at runtime. This effort should eliminate a lot of the current computations being made and a lot of the variables being used by the G-Code struct, because once the master has been established, and the parameters for the master has been filled, the main parameters for the slaves are the pin numbers, pin states, and the ratio to the master. By utilizing ratios within the master stepper driver loops, many of the computations required for the other axes will no longer be required.
I am slowly, but surely, becoming a little more enthused about this experiment.
If anybody cares to take a peek, I am attaching the source with all the modifications.
Written by Don Starkey between 2011 and 2013
CNC in Spin with Float-Math.
Haven't looked closely at it, since I am playing with my own code
Anyways I upload it for the interested.
Enjoy!
Mike
https://groups.google.com/forum/m/#!topic/machinekit/MsQHBIOJQv0
Some pretty interesting discussions.
Also:
http://www.machinekit.io/docs/code/Code_Notes/#motion-controller-introduction