I haven't included all the code, it's over 400,000 lines.
As soon as the angle gets steeper than 45° you'd have to exchange master/slave axis. The motors should stop only at the sharp edge when leaving the ground plane but not at every single G1 move.
>The slave would end up making a series of step and no-step cycles resulting in a frequency range of 5 to 10kHz.
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. ...
Two different frequencies for two different motors is no problem. Two (or more) different frequencies for the same motor is. Again, Bresenham with X moving 3000 steps while Y is moving 2000 steps does this:
* wait 100µs
* X executes one step, Y executes one step
* wait 100µs
* X executes one step
* wait 100µs
* X executes one step, Y executes one step
* repeat...
This results in unequal periods alternating between 10 and 20µs for the Y axis.
The ideal behaviour would be, instead:
* wait 100µs
* X executes one step, Y executes one step
* wait 100µs
* X executes one step
* wait 50µs
* Y executes one step
* wait 50µs
* X executes one step
* repeat...
This could be solved in two ways. a) increase your time resolution and extend Bresenham to be able to output less than one step per time slice for the master. In this example twice the masters frequency is sufficient. But to get good results for all possible frequency ratios much more is required. b) use different cogs for each axis.
If you think this does not make much difference, we have tested it. With the simple Bresenham aproach the motors stall at 2m/min. The motors make terrible noises like an old modem. With the improved timing the same motors run 9m/min without problems, smooth and with a clear sound. (NEMA24 motors at 48V 5A, 5mm pitch ball screw, ~25kg moving mass)
And yes, there are machines where this doesn't matter. Drilling machines, Pick&Place and so on need no trajectory planning. The do simple and straight point to point moves. But you won't impress anybody with it, these days. Sorry, I don't want to criticize you, I just think it can be done better, especially with the propeller.
As soon as the angle gets steeper than 45° you'd have to exchange master/slave axis. The motors should stop only at the sharp edge when leaving the ground plane but not at every single G1 move.
I will keep that in mind
But you won't impress anybody with it, these days.
I am not out to impress anyone with this idea, I am just seeking solutions and experimenting, to suit my needs and perhaps the needs of others who do need anything fancy schmancy.
This could be solved in two ways.
a) increase your time resolution and extend Bresenham to be able to output less than one step per time slice for the master. In this example twice the masters frequency is sufficient. But to get good results for all possible frequency ratios much more is required. b) use different cogs for each axis.
I think idbruce is planning on b) use different cogs for each axis ?
As you say, you can improve a) with finer time resolutions, but that makes upstream preparation more complex, and your finest time required is bumped to much less than the fastest step time. Meanwhile, those other COGS sit there....
Using a different COG for each axis, means each COG only has to 'keep up' with that axis step rate, and it can precisely meet the required rate.
I think idbruce is planning on b) use different cogs for each axis ?
That is correct.
After messing around with various alterations in the main code, I began working on the master axis code. If you take a peek at the following code, which is the ramp up loop for the master, you can clearly see that things start to get messy, and a bottle neck is surely developing, and in my opinion, will clearly will not work well, if at all.
while(current_move.master_ramp_up_steps != 0)
{
// Send out a high pulse on the step pin for the desired duration.
PHSA = -current_move.master_pulse_width;
step_counter++;
if(step_counter % (uint32_t)slave_1_integer_part == 0)
{
slave_1_offset = master_counter + (current_move.master_start_stop_speed *
slave_1_fractional_part);
}
if(step_counter % (uint32_t)slave_2_integer_part == 0)
{
}
if(step_counter % (uint32_t)slave_3_integer_part == 0)
{
}
// Wait for a specified period of time before sending another
// high pulse to the step pin.
waitcnt(master_counter += current_move.master_start_stop_speed
-= current_move.master_ramp_inc_dec);
// Decrement ramp_up_steps
current_move.master_ramp_up_steps--;
}
My next proposed solution, is to create four basically identical cogs, with each cog running the same exact loops. In other words, let's say that the master must make 2000 ramp up steps, well then the slave cogs should also be setup to iterate 2000 times during ramp up. So instead of have having three comparisons, within each loop, for each axis, within the master driver, and then updating the slave drivers, it then becomes limited to one comparison in each of the drivers, in each of the three loops.
Once again, in other words.... The are (4) master axis drivers... but three of them are just called slaves. Each of the slaves would simply compare the individual ratio.
...
My next proposed solution, is to create four basically identical cogs, with each cog running the same exact loops. In other words, let's say that the master must make 2000 ramp up steps, well then the slave cogs should also be setup to iterate 2000 times during ramp up. So instead of have having three comparisons, within each loop, for each axis, within the master driver, and then updating the slave drivers, it then becomes limited to one comparison in each of the drivers, in each of the three loops.
Once again, in other words.... The are (4) master axis drivers... but three of them are just called slaves. Each of the slaves would simply compare the individual ratio.
I'm only partly following.
I think Master and Slave is not a good choice of words, but if one COG needs 2000 steps, the others can be 1000, or 3000, with the condition that they take the same total time.
They need not be forced to tick over at 2000 steps, but they do need here 1:2:3 step rates-ratio, so the time-to-finish are identical.
That's quite easy to manage with your separate COGs.
That is completely okay, because I only partially follow what you try to tell me most of the time
And if it will make you feel better, from here on in, I will try to refer to them as master and followers.
Let me begin by explaining my scenario first, because either you see something that I don't, or you are not grasping the full picture.
The master, which has the most steps... during ramp up time, the rate of speed increases with every step that is made.... once the stepper reaches maximum speed, the rate of speed becomes constant... during ramp down time, the rate of speed decreases with every step that is made....
As I see it, it is not a simple matter of setting the various timers to the different ratios, because the speed of the master is always changing during ramp times. The ramps are what make the whole timing scenario difficult. If it was all a constant pace, it would have easily been solved years ago. Instead, the followers must follow the master, on a step by step basis, in order to maintain the correct pace during ramp up and ramp down procedures, and when the master is finished, the followers are finished also. So there is no reason to track the steps of the followers, but rather maintain the ratio of steps, according to the pace of the master.
In other words, it is not important that they finish at exactly the same time, because that will come naturally with the use of ratios. It is important however for the followers to maintain pace with the master.
They need not be forced to tick over at 2000 steps
You are absolutely correct, they do not, but in order to maintain pace with the master, the followers ratio should be checked against every tick of the master.
Each of these axes are running in separate cogs, but they all have the same start time, with the same amount of iterations, being 2000 iterations to match the step requirement of the master, and the same ramp increments/decrements.
After each step tick that a master would make, a comparison is made in each cog to test the various follower ratios, according to the masters total steps taken. If the integer part of a ratio is equally divisible to the masters total steps taken, for any given follower, then an additional (EDIT) step (EDIT END) timer is set within that cog, for the fractional portion of the ratio. This setup would allow the follower axes to maintain pace during ramp up and ramp down procedures of the master.
On the other hand, if the timers were simply set based upon ratios, they might finish at the same time, but the resulting line would be zigzag, because of the masters ramp procedures.
So in my opinion, the code for all four axes, should be exactly the same, with the exception of a ratio integer part and ratio fractional part, used for comparisons, and waintcnt time frame established for a particular follower.
I guess I don't understand why you don't use a standard discretized velocity profiler where you have:
Acceleration in steps/sec/sec
Slew in steps/sec
Deceleration in steps/sec/sec
How are you handling moves that are too short to even reach slew?
Surely you jest
Your comprehension over machine control is much more advanced than mine.
First off, I have no freakin' idea what a standard discretized velocity profiler is
But for the sake of discussion, my basic ultimate goal is to create a four axis step generator, for open loop synchronized control of stepper motors, using nothing more than the Propeller chip. And initially, this goal is just for linear travel, but hopefully it can be extended to include other types of movement.
How are you handling moves that are too short to even reach slew?
According to the context given, I am assuming that "slew" is desired maximum speed, since it is in between acceleration and deceleration.
If the master's travel is shorter than the desired maximum speed, the motor is simply ramped up to the half way point and then it begins it's ramp down to zero. Stepper motors should be ramped anywhere from 1~2 maximum revolutions per step, until maximum speed is reached. There will be many times (moves) when maximum speed will never be reached. As I am sure you are aware, steppers must build up their speed gradually, with proper ramping, otherwise they will stall or miss steps.
I hope I answered your question, if not, than you will have to rephrase it to match my comprehension level
my basic ultimate goal is to create a four axis step generator, for open loop synchronized control of stepper motors, using nothing more than the Propeller chip.
Although I am not at the point of testing, I just thought of something....
My current intention is to use the previously posted MOVE_STRUCT to provide parameters for each of the stepper drivers, although I have not tested it yet, I believe these parameters can be changed be during execution, and if not, then these parameters can be simply copied into variables, which can be altered during execution, and then these variables will be used as the driver parameters. I am really not sure what benefits can be achieved with this possibility, but I just wanted to make a note of it, before I forget it.
EDIT: Taking this a step further.... First off, there will be no hard coded parameters or constants used within any of the drivers. Therefore I should ensure that all variables, within each of the drivers, is capable of being altered during execution. There is currently no particular reason for setting it up this way, but by doing so, it will definitely provide the most flexibility for the driver.
Mickster : I guess I don't understand why you don't use a standard discretized velocity profiler where you have:
Acceleration in steps/sec/sec
Slew in steps/sec
Deceleration in steps/sec/sec
Surely you jest
Your comprehension over machine control is much more advanced than mine.
First off, I have no freakin' idea what a standard discretized velocity profiler is
It sounds way more complicated than it actually is
In parameter terms, you first pass the Slowest Step dT.
Then, you pass the DEC value, applied every Step, to decrease that dT, and a Count of how many times that is applied.
Next is the peak-slew-rate dT, which is the smallest value, and a Count
Finally, the INC value, to slow down, to increase from that smallest dT, also with a Count.
Strictly, a ++/-- on every step is not quite linear acceleration, but that may not matter here.
Choice is to either do more complex COG level maths per-step,
or, you could pass another param that is a DEC-Fix, to allow shape control of acceleration, (aka the change changes too)
or, you could repeat the Acceleration/Deceleration record, with a new modifier for each segment.
ie the upstream master calculator does more of the shape fitting.
That's a few params, but the operations are all easy ADD , SUB with some fractional 'apply sometimes' decisions.
( google binary rate multipliers for how to make the fractional decisions)
Strictly, a ++/-- on every step is not quite linear acceleration, but that may not matter here.
Ahhhh.... You better examine the code a tad closer
The ++ and -- are just operators used on the step counters to maintain a step count, and they are not used for ramping. The ramp increment/decrement variable sets the acceleration rate.
// Wait for a specified period of time before sending another
// high pulse to the step pin.
waitcnt(master_counter += current_move.master_start_stop_speed
-= current_move.master_ramp_inc_dec);
// Decrement ramp_up_steps
current_move.master_ramp_up_steps--;
EDIT: As mentioned in an earlier post:
Stepper motors should be ramped anywhere from 1~2 maximum revolutions per step, until maximum speed is reached.
The ramp increment/decrement variable sets the acceleration rate anywhere from 0 ~2 revs per step.
EDIT 2: However, I doubt that my current method is linear either
Ahhhh.... You better examine the code a tad closer
The ++ and -- are just operators used on the step counters to maintain a step count, and they are not used for ramping. The ramp increment/decrement variable sets the acceleration rate.
I was not talking about your code, but rather the general change-steps case Mickster mentioned.
// Wait for a specified period of time before sending another
// high pulse to the step pin.
waitcnt(master_counter += current_move.master_start_stop_speed
-= current_move.master_ramp_inc_dec);
// Decrement ramp_up_steps
current_move.master_ramp_up_steps--;
Yes, that is equivalent to the middle section, or the non-ramp part of Mickter's post, or the peak-slew-rate dT, which is the smallest dT value.
The ramp sections just take that dT, which may be too short to reach in one step, and use multiple steps to shrink down to that dT
With the exception of areas marked for alteration, I believe the master should look very similar to the following. And the follower drivers should be almost identical.
#include "master.h"
#include "config.h"
#include "move_struct.h"
#include "Gearing.h"
static int master_cog = 0;
static int master_stack[100];
volatile bool master_updated;
volatile bool move_complete;
volatile uint8_t master_dir;
volatile uint8_t master_dir_pin;
volatile uint8_t master_step_pin;
volatile uint32_t master_ramp_up_steps;
volatile uint32_t master_running_steps;
volatile uint32_t master_ramp_down_steps;
volatile uint32_t master_pulse_width;
volatile uint32_t master_start_stop_speed;
volatile uint32_t master_ramp_inc_dec;
void master(void *par);
void master_init()
{
if(master_driver_cog == 0)
{
master_cog = 1 + cogstart(&master, NULL,
master_stack, sizeof master_stack);
}
}
void master_stop()
{
if(master_cog > 0)
{
cogstop(master_cog - 1);
master_cog = 0;
}
}
void master(void *par)
{
do
{
if(update_master_now == true)
{
update_master_now = false;
// Set DIRA as an output.
DIRA |= 1 << master_dir_pin;
// Set up the CTRMODE of Counter A for NCO/PWM single-ended.
CTRA = (4 << 26) | master_step_pin;
// Set the value to be added to PHSA with every clock cycle.
FRQA = 1;
// Set DIRA as an output.
DIRA |= 1 << master_step_pin;
// Set the OUTA register to match the desired direction of rotation.
if(master_dir == 0)
{
OUTA &= (~1 << master_dir_pin);
}
else
{
OUTA |= 1 << master_dir_pin;
}
master_updated = true;
}
if(app_move_now == true)
{
app_move_now = false;
int32_t master_counter;
int32_t master_step_counter;
// Get the current System Counter value.
master_counter = CNT;
// Ramp up the stepper motor a predetermined amount of steps
while(master_ramp_up_steps != 0)
{
// Send out a high pulse on the step pin for the desired duration.
PHSA = -master_pulse_width;
master_step_counter++;
//////////////////////////////////////////////////////////////////
// Since this is the master driver, this comparison should be
// removed and replaced with a waitcnt equivalent to the time
// that it would take to run this code. However code similar
// to the following, should be placed within all of the follower
// drivers. Additionally, all of the follower drivers will be
// almost identical in respect to operation and flow, but the PHSA
// as used in this driver, will be replaced with a waitcnt, in the
// follower drivers.
//////////////////////////////////////////////////////////////////
if(step_counter % (uint32_t)follower_1_int_part == 0)
{
follower_1_offset = master_counter +
(master_start_stop_speed * follower_1_dec_part);
}
//////////////////////////////////////////////////////////////////
// Wait for a specified period of time before sending another
// high pulse to the step pin.
waitcnt(master_counter += master_start_stop_speed
-= master_ramp_inc_dec);
// Decrement ramp_up_steps
master_ramp_up_steps--;
}
// Run the stepper motor at the current speed for a predetermined
// number of steps
while(master_running_steps != 0)
{
// Send out a high pulse on the step pin for the desired duration.
PHSA = -master_pulse_width;
master_step_counter++;
//////////////////////////////////////////////////////////////////
// Since this is the master driver, this comparison should be
// removed and replaced with a waitcnt equivalent to the time
// that it would take to run this code. However code similar
// to the following, should be placed within all of the follower
// drivers. Additionally, all of the follower drivers will be
// almost identical in respect to operation and flow, but the PHSA
// as used in this driver, will be replaced with a waitcnt, in the
// follower drivers.
//////////////////////////////////////////////////////////////////
if(step_counter % (uint32_t)follower_1_int_part == 0)
{
follower_1_offset = master_counter +
(master_start_stop_speed * follower_1_dec_part);
}
//////////////////////////////////////////////////////////////////
// Wait for a specified period of time before sending another
// high pulse to the step pin.
waitcnt(master_counter += master_start_stop_speed);
// Decrement running_steps
master_running_steps--;
}
// Ramp down the stepper motor a predetermined amount of steps
while(master_ramp_down_steps != 0)
{
// Send out a high pulse on the step pin for the desired duration.
PHSA = -master_pulse_width;
master_step_counter++;
//////////////////////////////////////////////////////////////////
// Since this is the master driver, this comparison should be
// removed and replaced with a waitcnt equivalent to the time
// that it would take to run this code. However code similar
// to the following, should be placed within all of the follower
// drivers. Additionally, all of the follower drivers will be
// almost identical in respect to operation and flow, but the PHSA
// as used in this driver, will be replaced with a waitcnt, in the
// follower drivers.
//////////////////////////////////////////////////////////////////
if(step_counter % (uint32_t)follower_1_int_part == 0)
{
follower_1_offset = master_counter +
(master_start_stop_speed * follower_1_dec_part);
}
//////////////////////////////////////////////////////////////////
// Wait for a specified period of time before sending another
// high pulse to the step pin.
waitcnt(master_counter += master_start_stop_speed
+= master_ramp_inc_dec);
// Decrement ramp_down_steps
master_ramp_down_steps--;
}
move_complete = true;
}
}
while(master_cog > 0);
}
EDIT: Well now, that does not look good If interested, simply copy and paste into Notepad with appropriate margins, or you can shrink the browser page size.
There have been at least a couple instances within this thread, where I have been stating inaccurate information about ramp rates. I began to doubt myself, so I looked it up from a previous thread, approximately 5 years ago...
I that specific thread I stated:
Perhaps this may be useful information for you. One of the engineers at Applied Motion Products (http://www.applied-motion.com/), which happen to make stepper motors and stepper drives, told me that a ramping profile should either increase or decrease 3 to 5 revolutions per full revolution.
That sounds quite a bit more accurate and I offer my apologies for stating inaccurate information. My bad.
Even though I have more pressing matters, I have spent the last several days working on this endeavor. More specifically, over the last several days, I have been developing a test container application, for this concept, as well as working on the master and follower code.
Although not 100% certain, I believe I am now at a point, where I need to combine various test projects, into the main test container application. Once this has been done, then I believe it should be ready for just a few more tweaks.
The additional tweaks will include slowing down the timing on all four axes and make it adaptable to blinking LEDs, instead of pulsing stepper drivers. Basically I will be testing by blinking 4 LEDs at different timing ratios, but all based upon the masters step count, and letting it run for perhaps an hour, or even 24 hours, and ensuring they all stop in sync, with all axes having the proper step count.
If this works, I believe it will be a good thing. We will have access to synchronized multi-axis CNC code, which will be capable of ramping, without trajectory planning. Just set the appropriate parameters at startup or before each move and let her rip.
Nothing to do with your development, bud but I was having a quick glance at your code (at the pub) last night. Remembering that you have run in to memory constraints, in the past, it appears that you are using entire LONGs for flags(?) which not only consume memory but also, when you are setting/resetting more than one flag, you are generating more code than you would if you were masking bits in one LONG.
It's this sort of thing that makes me question C's premise of portability.
When I was writing my code for CNC or 3D printing, I was writing much of it hastily, and I have no doubt, that it could be optimized by a better programmer than me Especially now, since I have been altering code everywhere in my test container, there is quite a bit of redundant code. My main goal for now is to just get something working.
I started testing last night and discovered a couple problems that I had overlooked. During the transfer from either SPIN to C, or C program to C program, there were several parameters that were not being initialized. I eventually resolved those issues, but now the program is hanging during runtime. I currently believe the system clock is rolling past my code timing, since the loops were especially tight for SPIN to start with. Hopefully I can get it working sometime today, or at least to the point to where a sharper mind than mine can get it to work.
Remembering that you have run in to memory constraints, in the past, it appears that you are using entire LONGs for flags(?) which not only consume memory but also, when you are setting/resetting more than one flag, you are generating more code than you would if you were masking bits in one LONG.
I noticed another issue during the development of the test container and someone told me about this several years ago, but I had forgotten about it. While writing the test container, I commented out SD reader related code, and made it so that a user could input one line of pseudo G-Code, into the serial terminal. With this simple alteration, the build size dropped tremendously. I don't know or simply don't remember if this is true for SPIN, but when adding SD reading capabilities to a C project, the build size increases significantly.
In addition to optimizing my code... Which definitely needs optimization... Perhaps the SD card reader code could be optimized also. I do not know if it can be optimized further, but if it can, then that should shrink the code significantly also. Additionally, I would also imagine, that if the use of the Simple libraries were eliminated, and only GCC were used, it would also drop the build size significantly. I am not certain, but I think so.
With all that being said, if optimization of the code and/or switching to GCC can significantly reduce build size, and if I can get this code to pan out, then I believe we may have something worth pursuing.
In addition to my previous comments and in reference to your comment...
Remembering that you have run in to memory constraints, in the past, it appears that you are using entire LONGs for flags(?) which not only consume memory but also, when you are setting/resetting more than one flag, you are generating more code than you would if you were masking bits in one LONG.
When grabbing code for my test container, I grabbed the code that I believed would be the easiest to alter for testing purposes. If I am not mistaken, I believe Jason help me resolve those flag issues in a later version of similar code, and if not, he definitely helped me with setting a variety of flags, within a single LONG, for the bitmap thread. So yes, there are several things that can be done to reduce code size, as well as making it run more efficiently. At the present, my current endeavor requires floating point support, but as indicated, by numerous members, the use of doubles or floating point can easily be eliminated.
I currently believe the system clock is rolling past my code timing, since the loops were especially tight for SPIN to start with.
Up to this point, all of my stepper driver code, which has been posted throughout the forum, has been based upon the use of the Gecko G251 and the G251X microstepping drives. The G251 requires a minimum high pulse width of 1uS. In the past, all of my drivers have been using the Propeller counters, set up in NCO/PWM single-ended mode, with the PHSA accumulator of the counter handling the G251 1uS delay, without a problem. However with the master/follower code that I am now working on, only the master driver utilizes a counter, whereas the follower drivers are driven with the high pulse widths handled by WAITCNT(s). The minimum offset from CNT, when using WAITCNT is 381 cycles, so the 1uS delay just wasn't cutting the mustard, and causing the clock to roll over.
Many, many years ago, when several forum members were helping me to write my first stepper driver, JonnyMac, who was one of those helpful members, advised me to provide a duration higher then the minimal high pulse width for the step pin, but upon noticing a decrease in speed, I was stubborn and stuck with the 1uS delay. Now many years later, this 1uS delay has come back to bite me in the rear, and has caused me a days worth of programming.
I should have listened to JonnyMac Or at least as it applies in this specific instance Okay, okay, he always gives sound advice.... Don't get your Propeller chips in a bunch
Anyhow.... At this point, the minimum high pulse width for all stepper drivers will have to be at least 381 cycles.
The G251 requires a minimum high pulse width of 1uS. In the past, all of my drivers have been using the Propeller counters, set up in NCO/PWM single-ended mode, with the PHSA accumulator of the counter handling the G251 1uS delay, without a problem. However with the master/follower code that I am now working on, only the master driver utilizes a counter, whereas the follower drivers are driven with the high pulse widths handled by WAITCNT(s). The minimum offset from CNT, when using WAITCNT is 381 cycles, so the 1uS delay just wasn't cutting the mustard, and causing the clock to roll over....
Anyhow.... At this point, the minimum high pulse width for all stepper drivers will have to be at least 381 cycles.
Sounds like that 381 varies with revisions so is less than ideal..
NCO should still work ? From my reading, you can preset everything then start it, and outputs PHSx[31], so with careful start phase, you can generate any 'one shot' pulse width, and not need WAITs, but there is a side effect, that at some stage you do need to reload/reconfigure the Counter, before the next rise of PHSx[31] - however, that's a long time...
Sounds like that 381 varies with revisions so is less than ideal..
NCO should still work ? From my reading, you can preset everything then start it, and outputs PHSx[31], so with careful start phase, you can generate any 'one shot' pulse width, and not need WAITs, but there is a side effect, that at some stage you do need to reload/reconfigure the Counter, before the next rise of PHSx[31] - however, that's a long time...
Hmmm...
I never had any issues with pulses that I have sent for small distances, 12 inches max on 1/10 microstepping driver, or maybe I had issues and didn't even recognize it. Anyway, here is how I used it with SyncroStepper
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
Alright folks, even though it is not working at this point, because of some issues with floating point bugs, I am now posting a copy of the test container, so that others may assist, if they are so inclined. The main issue is in all of the follower drivers at this point, and it involves floating point support. This issue must be resolved, before this experiment can go any further. For more information pertaining to this issue, please refer to this thread: forums.parallax.com/discussion/166952/mothra-major-propeller-problem-or-at-least-to-me-it-is
Anyhow, here is what I have for a test container so far. It will build and run, but it will hang in the follower driver cogs, after submitting a line of G-Code, because of the issues discussed in the thread linked to above.
Also, please excuse the mess within the project, because it is a work in progress, with quite a bit of redundancy.
You establish the value of gcode_struct_element and then in every case, use the same literal number for the array element assignment. This assignment need only happen once, using the value of gcode_struct_element.
Further down, val_counter is incremented in all cases. This can be simplified.
Are you referring to this section of code? If so, it looks like the value of gcode_struct_element is being set according to the value of gcode_char.
else if(gcode_char == 'G')
{
// Set current field element
gcode_struct_element = 0;
current_gcode.valid[0] = '1';
}
else if(gcode_char == 'M')
{
// Set current field element
gcode_struct_element = 1;
current_gcode.valid[1] = '1';
}
else if(gcode_char == 'T')
{
// Set current field element
gcode_struct_element = 2;
current_gcode.valid[2] = '1';
}
else if(gcode_char == 'S')
{
// Set current field element
gcode_struct_element = 3;
current_gcode.valid[3] = '1';
}
else if(gcode_char == 'P')
{
// Set current field element
gcode_struct_element = 4;
current_gcode.valid[4] = '1';
}
else if(gcode_char == 'X')
{
// Set current field element
gcode_struct_element = 5;
current_gcode.valid[5] = '1';
}
else if(gcode_char == 'Y')
{
// Set current field element
gcode_struct_element = 6;
current_gcode.valid[6] = '1';
}
else if(gcode_char == 'Z')
{
// Set current field element
gcode_struct_element = 7;
current_gcode.valid[7] = '1';
}
else if(gcode_char == 'E')
{
// Set current field element
gcode_struct_element = 8;
current_gcode.valid[8] = '1';
}
else if(gcode_char == 'F')
{
// Set current field element
gcode_struct_element = 9;
current_gcode.valid[9] = '1';
}
else
{
I must admit then I am lost. Could you please provide an example?
Further down, val_counter is incremented in all cases. This can be simplified.
I would assume you are referring to value_counter. That is not the case. value_counter is only incremented, if a specific condition is met, and then the parent conditional statement is exited, thus going through the next iteration of the for loop. However, I may be misunderstanding you, and you may be making a good point, so if you could please provide an example for this also, I would greatly appreciate it.
Comments
Any curve that is not a perfect line or arc (part of a circle) must be approximated by lots of short line segments. Here is an example:
I haven't included all the code, it's over 400,000 lines.
As soon as the angle gets steeper than 45° you'd have to exchange master/slave axis. The motors should stop only at the sharp edge when leaving the ground plane but not at every single G1 move.
Two different frequencies for two different motors is no problem. Two (or more) different frequencies for the same motor is. Again, Bresenham with X moving 3000 steps while Y is moving 2000 steps does this:
* wait 100µs
* X executes one step, Y executes one step
* wait 100µs
* X executes one step
* wait 100µs
* X executes one step, Y executes one step
* repeat...
This results in unequal periods alternating between 10 and 20µs for the Y axis.
The ideal behaviour would be, instead:
* wait 100µs
* X executes one step, Y executes one step
* wait 100µs
* X executes one step
* wait 50µs
* Y executes one step
* wait 50µs
* X executes one step
* repeat...
This could be solved in two ways. a) increase your time resolution and extend Bresenham to be able to output less than one step per time slice for the master. In this example twice the masters frequency is sufficient. But to get good results for all possible frequency ratios much more is required. b) use different cogs for each axis.
If you think this does not make much difference, we have tested it. With the simple Bresenham aproach the motors stall at 2m/min. The motors make terrible noises like an old modem. With the improved timing the same motors run 9m/min without problems, smooth and with a clear sound. (NEMA24 motors at 48V 5A, 5mm pitch ball screw, ~25kg moving mass)
And yes, there are machines where this doesn't matter. Drilling machines, Pick&Place and so on need no trajectory planning. The do simple and straight point to point moves. But you won't impress anybody with it, these days. Sorry, I don't want to criticize you, I just think it can be done better, especially with the propeller.
I will keep that in mind
I am not out to impress anyone with this idea, I am just seeking solutions and experimenting, to suit my needs and perhaps the needs of others who do need anything fancy schmancy.
As you say, you can improve a) with finer time resolutions, but that makes upstream preparation more complex, and your finest time required is bumped to much less than the fastest step time. Meanwhile, those other COGS sit there....
Using a different COG for each axis, means each COG only has to 'keep up' with that axis step rate, and it can precisely meet the required rate.
That is correct.
After messing around with various alterations in the main code, I began working on the master axis code. If you take a peek at the following code, which is the ramp up loop for the master, you can clearly see that things start to get messy, and a bottle neck is surely developing, and in my opinion, will clearly will not work well, if at all.
My next proposed solution, is to create four basically identical cogs, with each cog running the same exact loops. In other words, let's say that the master must make 2000 ramp up steps, well then the slave cogs should also be setup to iterate 2000 times during ramp up. So instead of have having three comparisons, within each loop, for each axis, within the master driver, and then updating the slave drivers, it then becomes limited to one comparison in each of the drivers, in each of the three loops.
Once again, in other words.... The are (4) master axis drivers... but three of them are just called slaves. Each of the slaves would simply compare the individual ratio.
I'm only partly following.
I think Master and Slave is not a good choice of words, but if one COG needs 2000 steps, the others can be 1000, or 3000, with the condition that they take the same total time.
They need not be forced to tick over at 2000 steps, but they do need here 1:2:3 step rates-ratio, so the time-to-finish are identical.
That's quite easy to manage with your separate COGs.
I am sure that you and I can get this to work.
That is completely okay, because I only partially follow what you try to tell me most of the time
And if it will make you feel better, from here on in, I will try to refer to them as master and followers.
Let me begin by explaining my scenario first, because either you see something that I don't, or you are not grasping the full picture.
The master, which has the most steps... during ramp up time, the rate of speed increases with every step that is made.... once the stepper reaches maximum speed, the rate of speed becomes constant... during ramp down time, the rate of speed decreases with every step that is made....
As I see it, it is not a simple matter of setting the various timers to the different ratios, because the speed of the master is always changing during ramp times. The ramps are what make the whole timing scenario difficult. If it was all a constant pace, it would have easily been solved years ago. Instead, the followers must follow the master, on a step by step basis, in order to maintain the correct pace during ramp up and ramp down procedures, and when the master is finished, the followers are finished also. So there is no reason to track the steps of the followers, but rather maintain the ratio of steps, according to the pace of the master.
In other words, it is not important that they finish at exactly the same time, because that will come naturally with the use of ratios. It is important however for the followers to maintain pace with the master.
You are absolutely correct, they do not, but in order to maintain pace with the master, the followers ratio should be checked against every tick of the master.
So let's assume this...
Master... 2000 steps
Follower 1... 1000 steps
Follower 2... 500 steps
Follower 3... 250 steps
Each of these axes are running in separate cogs, but they all have the same start time, with the same amount of iterations, being 2000 iterations to match the step requirement of the master, and the same ramp increments/decrements.
Master... 2000 iterations
Follower 1... 2000 iterations
Follower 2... 2000 iterations
Follower 3... 2000 iterations
After each step tick that a master would make, a comparison is made in each cog to test the various follower ratios, according to the masters total steps taken. If the integer part of a ratio is equally divisible to the masters total steps taken, for any given follower, then an additional (EDIT) step (EDIT END) timer is set within that cog, for the fractional portion of the ratio. This setup would allow the follower axes to maintain pace during ramp up and ramp down procedures of the master.
On the other hand, if the timers were simply set based upon ratios, they might finish at the same time, but the resulting line would be zigzag, because of the masters ramp procedures.
So in my opinion, the code for all four axes, should be exactly the same, with the exception of a ratio integer part and ratio fractional part, used for comparisons, and waintcnt time frame established for a particular follower.
Acceleration in steps/sec/sec
Slew in steps/sec
Deceleration in steps/sec/sec
How are you handling moves that are too short to even reach slew?
Surely you jest
Your comprehension over machine control is much more advanced than mine.
First off, I have no freakin' idea what a standard discretized velocity profiler is
But for the sake of discussion, my basic ultimate goal is to create a four axis step generator, for open loop synchronized control of stepper motors, using nothing more than the Propeller chip. And initially, this goal is just for linear travel, but hopefully it can be extended to include other types of movement.
According to the context given, I am assuming that "slew" is desired maximum speed, since it is in between acceleration and deceleration.
If the master's travel is shorter than the desired maximum speed, the motor is simply ramped up to the half way point and then it begins it's ramp down to zero. Stepper motors should be ramped anywhere from 1~2 maximum revolutions per step, until maximum speed is reached. There will be many times (moves) when maximum speed will never be reached. As I am sure you are aware, steppers must build up their speed gradually, with proper ramping, otherwise they will stall or miss steps.
I hope I answered your question, if not, than you will have to rephrase it to match my comprehension level
Although I am not at the point of testing, I just thought of something....
My current intention is to use the previously posted MOVE_STRUCT to provide parameters for each of the stepper drivers, although I have not tested it yet, I believe these parameters can be changed be during execution, and if not, then these parameters can be simply copied into variables, which can be altered during execution, and then these variables will be used as the driver parameters. I am really not sure what benefits can be achieved with this possibility, but I just wanted to make a note of it, before I forget it.
EDIT: Taking this a step further.... First off, there will be no hard coded parameters or constants used within any of the drivers. Therefore I should ensure that all variables, within each of the drivers, is capable of being altered during execution. There is currently no particular reason for setting it up this way, but by doing so, it will definitely provide the most flexibility for the driver.
In parameter terms, you first pass the Slowest Step dT.
Then, you pass the DEC value, applied every Step, to decrease that dT, and a Count of how many times that is applied.
Next is the peak-slew-rate dT, which is the smallest value, and a Count
Finally, the INC value, to slow down, to increase from that smallest dT, also with a Count.
Strictly, a ++/-- on every step is not quite linear acceleration, but that may not matter here.
Choice is to either do more complex COG level maths per-step,
or, you could pass another param that is a DEC-Fix, to allow shape control of acceleration, (aka the change changes too)
or, you could repeat the Acceleration/Deceleration record, with a new modifier for each segment.
ie the upstream master calculator does more of the shape fitting.
That's a few params, but the operations are all easy ADD , SUB with some fractional 'apply sometimes' decisions.
( google binary rate multipliers for how to make the fractional decisions)
Ahhhh.... You better examine the code a tad closer
The ++ and -- are just operators used on the step counters to maintain a step count, and they are not used for ramping. The ramp increment/decrement variable sets the acceleration rate.
EDIT: As mentioned in an earlier post:
The ramp increment/decrement variable sets the acceleration rate anywhere from 0 ~2 revs per step.
EDIT 2: However, I doubt that my current method is linear either
I was not talking about your code, but rather the general change-steps case Mickster mentioned.
Yes, that is equivalent to the middle section, or the non-ramp part of Mickter's post, or the peak-slew-rate dT, which is the smallest dT value.
The ramp sections just take that dT, which may be too short to reach in one step, and use multiple steps to shrink down to that dT
EDIT: Well now, that does not look good If interested, simply copy and paste into Notepad with appropriate margins, or you can shrink the browser page size.
There have been at least a couple instances within this thread, where I have been stating inaccurate information about ramp rates. I began to doubt myself, so I looked it up from a previous thread, approximately 5 years ago...
I that specific thread I stated:
That sounds quite a bit more accurate and I offer my apologies for stating inaccurate information. My bad.
EDIT: For more information pertaining to this subject, please review this thread, where I also made misleading statements
forums.parallax.com/discussion/142601/generating-a-step-table
Even though I have more pressing matters, I have spent the last several days working on this endeavor. More specifically, over the last several days, I have been developing a test container application, for this concept, as well as working on the master and follower code.
Although not 100% certain, I believe I am now at a point, where I need to combine various test projects, into the main test container application. Once this has been done, then I believe it should be ready for just a few more tweaks.
The additional tweaks will include slowing down the timing on all four axes and make it adaptable to blinking LEDs, instead of pulsing stepper drivers. Basically I will be testing by blinking 4 LEDs at different timing ratios, but all based upon the masters step count, and letting it run for perhaps an hour, or even 24 hours, and ensuring they all stop in sync, with all axes having the proper step count.
If this works, I believe it will be a good thing. We will have access to synchronized multi-axis CNC code, which will be capable of ramping, without trajectory planning. Just set the appropriate parameters at startup or before each move and let her rip.
I hope it works.
It's this sort of thing that makes me question C's premise of portability.
I appreciate your input.
When I was writing my code for CNC or 3D printing, I was writing much of it hastily, and I have no doubt, that it could be optimized by a better programmer than me Especially now, since I have been altering code everywhere in my test container, there is quite a bit of redundant code. My main goal for now is to just get something working.
I started testing last night and discovered a couple problems that I had overlooked. During the transfer from either SPIN to C, or C program to C program, there were several parameters that were not being initialized. I eventually resolved those issues, but now the program is hanging during runtime. I currently believe the system clock is rolling past my code timing, since the loops were especially tight for SPIN to start with. Hopefully I can get it working sometime today, or at least to the point to where a sharper mind than mine can get it to work.
I noticed another issue during the development of the test container and someone told me about this several years ago, but I had forgotten about it. While writing the test container, I commented out SD reader related code, and made it so that a user could input one line of pseudo G-Code, into the serial terminal. With this simple alteration, the build size dropped tremendously. I don't know or simply don't remember if this is true for SPIN, but when adding SD reading capabilities to a C project, the build size increases significantly.
In addition to optimizing my code... Which definitely needs optimization... Perhaps the SD card reader code could be optimized also. I do not know if it can be optimized further, but if it can, then that should shrink the code significantly also. Additionally, I would also imagine, that if the use of the Simple libraries were eliminated, and only GCC were used, it would also drop the build size significantly. I am not certain, but I think so.
With all that being said, if optimization of the code and/or switching to GCC can significantly reduce build size, and if I can get this code to pan out, then I believe we may have something worth pursuing.
In addition to my previous comments and in reference to your comment...
When grabbing code for my test container, I grabbed the code that I believed would be the easiest to alter for testing purposes. If I am not mistaken, I believe Jason help me resolve those flag issues in a later version of similar code, and if not, he definitely helped me with setting a variety of flags, within a single LONG, for the bitmap thread. So yes, there are several things that can be done to reduce code size, as well as making it run more efficiently. At the present, my current endeavor requires floating point support, but as indicated, by numerous members, the use of doubles or floating point can easily be eliminated.
I have located at least one of my problems...
Up to this point, all of my stepper driver code, which has been posted throughout the forum, has been based upon the use of the Gecko G251 and the G251X microstepping drives. The G251 requires a minimum high pulse width of 1uS. In the past, all of my drivers have been using the Propeller counters, set up in NCO/PWM single-ended mode, with the PHSA accumulator of the counter handling the G251 1uS delay, without a problem. However with the master/follower code that I am now working on, only the master driver utilizes a counter, whereas the follower drivers are driven with the high pulse widths handled by WAITCNT(s). The minimum offset from CNT, when using WAITCNT is 381 cycles, so the 1uS delay just wasn't cutting the mustard, and causing the clock to roll over.
Many, many years ago, when several forum members were helping me to write my first stepper driver, JonnyMac, who was one of those helpful members, advised me to provide a duration higher then the minimal high pulse width for the step pin, but upon noticing a decrease in speed, I was stubborn and stuck with the 1uS delay. Now many years later, this 1uS delay has come back to bite me in the rear, and has caused me a days worth of programming.
I should have listened to JonnyMac Or at least as it applies in this specific instance Okay, okay, he always gives sound advice.... Don't get your Propeller chips in a bunch
Anyhow.... At this point, the minimum high pulse width for all stepper drivers will have to be at least 381 cycles.
Sounds like that 381 varies with revisions so is less than ideal..
NCO should still work ? From my reading, you can preset everything then start it, and outputs PHSx[31], so with careful start phase, you can generate any 'one shot' pulse width, and not need WAITs, but there is a side effect, that at some stage you do need to reload/reconfigure the Counter, before the next rise of PHSx[31] - however, that's a long time...
Hmmm...
I never had any issues with pulses that I have sent for small distances, 12 inches max on 1/10 microstepping driver, or maybe I had issues and didn't even recognize it. Anyway, here is how I used it with SyncroStepper
Anyhow, here is what I have for a test container so far. It will build and run, but it will hang in the follower driver cogs, after submitting a line of G-Code, because of the issues discussed in the thread linked to above.
Also, please excuse the mess within the project, because it is a work in progress, with quite a bit of redundancy.
"current_move.master_total_steps % 2;”
”%” ???
I found this in at least 4 places. Only viewing on my phone but yeah, regarding redundancy, this appears to be replicated.
Edit: Ah modulus operator...should've Googled first
I never really used the modulus operator, until I started programming for uCs. Now I find it indispensable
I guess it is fair to say that I have learned a lot since knocking on Parallax's front door.
You establish the value of gcode_struct_element and then in every case, use the same literal number for the array element assignment. This assignment need only happen once, using the value of gcode_struct_element.
Further down, val_counter is incremented in all cases. This can be simplified.
I am not following....
Are you referring to this section of code? If so, it looks like the value of gcode_struct_element is being set according to the value of gcode_char.
I must admit then I am lost. Could you please provide an example?
I would assume you are referring to value_counter. That is not the case. value_counter is only incremented, if a specific condition is met, and then the parent conditional statement is exited, thus going through the next iteration of the for loop. However, I may be misunderstanding you, and you may be making a good point, so if you could please provide an example for this also, I would greatly appreciate it.