Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spindle ramping feature with fixed parameters. #295

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

TheAntTeam
Copy link

Hello everybody,

we are experimenting an electronic board to move to FluidNC for our project The Ant.

Our CNC uses a brushless motor for the spindle driven by an ESC.

If this motor is accelerated or decelerated abruptly, it causes peaks of current in the supply lines, that could cause noise and, above all, damages to electronics.

To avoid this issue, in our current firmware, we implemented a ramp-up/ramp-down feature, that is just a way to accelerate and decelerate smoothly the spindle.

This modification was very effective and measuring with the oscilloscopes we noticed no undervoltage or overvoltage in the supply lines, like before.

Would it be possible to implement the same feature also in FluidNC?

We experimented and propose a very draft modification, to have a base to talk about a better implementation.

@MitchBradley
Copy link
Collaborator

Any ideas for how to configure it?

@bdring
Copy link
Owner

bdring commented Feb 14, 2022

Laser inherits from PWM without overriding set_output(uint32_t duty)

I think this should be a new class inheriting from PWM.

@TheAntTeam
Copy link
Author

Any ideas for how to configure it?

We thought two possible ways up to now:

  • one parameter: a total time, ranging from 0 (disabled) to a maximum (e.g. 2 seconds), with a fixed number of interpolation points
  • two parameters: the number of interpolation points and the delay/time interval between points.

@TheAntTeam
Copy link
Author

Laser inherits from PWM without overriding set_output(uint32_t duty)

I think this should be a new class inheriting from PWM.

Yes, we think so too.

@MitchBradley
Copy link
Collaborator

It is sort of like acceleration.

@bdring
Copy link
Owner

bdring commented Feb 14, 2022

We also do not accept PRs against main.

https://github.com/bdring/FluidNC/wiki/Pull-Request-Guidelines

@MitchBradley
Copy link
Collaborator

So, the thing about Bresenham is that it hits the intermediate points as accurately as possible. That isn't a bad thing, but neither is it really necessary for a ramp-up to avoid overcurrent. Is there a simpler formulation that is good enough?

@TheAntTeam
Copy link
Author

So, the thing about Bresenham is that it hits the intermediate points as accurately as possible. That isn't a bad thing, but neither is it really necessary for a ramp-up to avoid overcurrent. Is there a simpler formulation that is good enough?

Maybe something with a fixed step size with a fixed delay?

@MitchBradley
Copy link
Collaborator

MitchBradley commented Feb 14, 2022

If you treat it as a max acceleration problem instead of always inventing a ramp regardless of the total delta speed, then it looks rather different. Instead of bresenham you just have a max increment, and you keep adding or subtracting that until you reach the target. It seems to me that applying a ramp to small speed changes is pointless.

while (current_speed <= target_speed) {
    delta_speed = min(target_speed - current_speed, max_delta);
    current_speed += delta_speed;
    delay(something);
}

@MitchBradley
Copy link
Collaborator

The parameter could be maximum percentage change per step, with the spinup delay giving the time to change 100%. So if %change is 10% and spinup delay is 3 seconds, each step takes 300 ms.

@TheAntTeam
Copy link
Author

Hi @MitchBradley, I will try it with a bit of testing, and I'll also implementi the changes @bdring suggested for the class and PR, and get back to you.
Thanks for the help!

@TheAntTeam
Copy link
Author

TheAntTeam commented Mar 1, 2022

Just did a couple of test with the solution @MitchBradley proposed, but writing some values as constant numbers for now.

        printf("cupwm: %d, duty: %d\n", _current_pwm_duty, duty);  //debug print
        while (_current_pwm_duty != duty) {  
            int32_t delta_speed;  
            if (duty > _current_pwm_duty) {  
                delta_speed = (duty - _current_pwm_duty) < 1024 ? (duty - _current_pwm_duty) : 1024;  
            }  
            else {
                delta_speed = (_current_pwm_duty - duty) < 1024 ? (duty - _current_pwm_duty) : -1024;
            }
            _current_pwm_duty = (_current_pwm_duty + delta_speed);

            printf("cupwm: %d, delta_speed: %d\n", _current_pwm_duty, delta_speed); //debug print
            ledcSetDuty(_pwm_chan_num, _current_pwm_duty);
            delay_ms(100);
        }

I tested with an oscilloscope and it works pretty well, taking care to use reasonable parameters value.
In this case with a step of 1024 and a max value of 65536, the total time to pass from 0 to the max value would be 6.4 seconds (that is too much, but it's easier for me to see when making changes less than 1024).
Likely the difference between unsigned values (_current_pwm_duty - duty) is implicitly cast, because it is assigned to an int32.

@bdring
Copy link
Owner

bdring commented Mar 1, 2022

It is important to have a user friendly set of config values. These should be in real world units. Something like a full scale maximum ramp time in milliseconds. Have to program do the math and not the user.

You should use log_debug(...) for debugging info.

@bdring bdring changed the title Ramping feature with fixed parameters. Spindle ramping feature with fixed parameters. Mar 4, 2022
@bdring
Copy link
Owner

bdring commented Mar 4, 2022

The easiest way to implement this might be to add a single new boolean item: ramp_on: with a default of false.

If this is true it uses spinup_ms for the ramp timing. It would internally calculate everything else it needs.

Laser inherits from PWM, but it already overrides the creation of config items, so it would not be affected.

@MitchBradley
Copy link
Collaborator

Boolean doesn't do it because you need to know how many steps in the ramp. Here is my earlier suggestion:

The parameter could be maximum percentage change per step, with the spinup delay giving the time to change 100%. So if %change is 10% and spinup delay is 3 seconds, each step takes 300 ms.
Default value is 100%

@bdring
Copy link
Owner

bdring commented Mar 4, 2022

Could the steps be done in a fixed time? Then it could be calculated. Is there a downside to doing short delays?

@MitchBradley
Copy link
Collaborator

The problem is essentially identical to the acceleration problem in motion where you have to ramp up to cruising speed. The motion parameter is the time derivative of the max speed parameter, in other words the maximum change of speed in a given time. In motion we do not have a fixed number of intervals for the ramp, but rather accelerate at the max rate until cruising speed is achieved.

For most spindles, the control signal can be changed instantly to the new value and the motor or vfd will deal with it. The motor drive will have an inherent hardware current limit, or the VFD will be smart about applying a ramp-up internally.

But, according to this ticket, there are spindles where the hardware can apply more instant power than is desirable. To handle both "ordinary" and "must limit in software", there needs to be a parameter to adjust what that power is.

A boolean that enables a fixed number of ramp steps does not give us the necessary degree of control. If, for example, the fixed number of steps is 8, then the max power is 1/8 of the full-on power. But what if 1/2 works just fine?

The percentage parameterization mimics the motion model. If max% is 20, then any speed change of less than 20% of full speed will be done in one bump of the PWM or whatever, and so on until a 0 to full-on speed change would be divided into 5 bumps.

An alternative would the reciprocal of max%, i.e. the number of bumps to full speed.

Either way, you add one numeric parameter that lets you adjust the thing you need. A boolean with a fixed number of steps makes us guess, and set in stone, what the appropriate adjustment factor is.

@bdring
Copy link
Owner

bdring commented Mar 4, 2022

You already know the total time and the range, therefore you can determine the acceleration.

By fixed time, I mean the value changes every 50ms (TBD). You use the other parameters to choose how much it changes on each interval.

@MitchBradley
Copy link
Collaborator

MitchBradley commented Mar 4, 2022

Having said all that, I totally missed the fundamental problem with this patch - you must not delay in set_output().

Spindle::set_output() is called from the stepper interrupt service routine via the call chain pulse_func .. spindle->setSpeedfromISR .. set_output . Delaying will stall the interrupt and cause all manner of havoc.

The way to make it work would be to integrate the spindle speed ramp into the segment generator code, having that split the spindle speed change into bumps in the same way that the segment generator does acceleration for motion. That would require some very careful work.

Edit - actually it is not the segment generator that needs to change, because segments are inherently too short to be of use in this scenario. It needs to be done in Spindle::setState() which is not used in an ISR, but rather from the GCode parser after synchronization.

@MitchBradley
Copy link
Collaborator

By fixed time, I mean the value changes every 50ms (TBD). You use the other parameters to choose how much it changes on each interval.

I really dislike wiring in an assumption when you can easily, with no extra config variables, make it configurable.

Or is the current suggestion that we have no new config variables, and always do a ramp?

@bdring
Copy link
Owner

bdring commented Mar 4, 2022

If you request a ramp, it uses the spinup_ms for the full range ramp duration. It determines an amount per interval that the speed increases in the init(). We could either use a fast, one size fits all, interval or calculate an optimized one in init(). The spinup_ms is not used for any delay when a ramp is used.

The user only cares about how much time they want to give it. They already have the config values to do that.

It is not much different than what goes on in a VFD. You give it max speed and a duration. The user does not need to know what is done to make that happen.

@MitchBradley
Copy link
Collaborator

What information is used to optimize the interval?

@bdring
Copy link
Owner

bdring commented Mar 4, 2022

I don't know that we need to optimize it. If we find a reason, we could do it. It is better than Joe StepStick trying to optimize it.

@MitchBradley
Copy link
Collaborator

The more I think about this the more complicated it gets. I have some code to implement a ramp in setState(), but I don't like any of the parameterizations.

The problem is that spindles are all over the map as to their response to changes in the control variable.

For some, the control variable represents a target speed directly, and smarts in the spindle electronics translate that to time-dependent changes in voltage or current or waveforms necessary to hit that target speed, sometimes with feedback.

For others, the control variable is, more or less, the power that is applied to the motor. It will change speed until the losses are in equilibrium with the power. When that equilibrium is reached, you can think of the control variable as correlating to the spindle speed. Initially the excess power of (new_speed - old_speed) accelerates the motor, but as losses increase, the acceleration drops off. So the speed curve probably looks more like (1 - exp(-t/t0)), similar to a capacitor charging.

For the second kind, if you ramp up the control variable value instead of setting it instantly to the desired value, the spindle will probably take quite a lot longer to spin up than it otherwise would. During the first step of the ramp, the excess power relative to the previous power is 1/N times the excess power that would be applied during a single-step profile. So the acceleration is limited. Instead of a fast ramp, you get a sequence of shallow "capacitor charge curves".

Based on this analysis, I am really concerned that fixed intervals will not give us a useful level of control.

@bdring
Copy link
Owner

bdring commented Mar 5, 2022

I have a working branch. The ramp is linear. I think that is what people expect and what VFDs do. People just pick a ramp length that yields the results they desire. it may not be the perfectly optimal profile, but it is easy to understand and tune. Let's tackle S curve on the axes before we do it on the spindle.

I added ramping to an Inventables fork of Grbl in the past. The only reason we did it was because it looked cool and sounded better. The spindle hit a brief resonance that cause a vibration if you cut power and let the spindle free spin to a stop. A free spin stop took about 3 seconds, a ramp down of 5 seconds fixed it.

In all cases were the user has control of some parameters, we need to test for problems in the init(). For example: if someone has a extremely low maxSpeed or spinup time, the ramp would be too coarse to work with. A maxSpeed of less than a few hundred is probably a typo anyway.

I have it ramping on increases or decreases in speed. I have not tested overrides yet.

isRateAdjusted() spindles are not allowed to use this.

@MitchBradley
Copy link
Collaborator

I pushed a branch LimitSpindleDelta where the parameters are _up_max_delta_dev_speed and _down_max_delta_dev_speed . They limit the step change in the final control parameter for the spindle. They default to 0, which means no limit. I think those parameters solve the problem as posed fairly directly.

@MitchBradley
Copy link
Collaborator

A solution to the original problem might not require limiting the down step - but ramp-down is indeed valuable in some cases. I had to limit it in hardware on my Consew spindle which could stop so fast - with servo braking - that the threaded tool holder would release from the spindle.

@bdring
Copy link
Owner

bdring commented Mar 5, 2022

Wow. On my 2.2kW VFD spindle, I have no break, but ramp down in about 6 seconds. If there is a power failure the spindle takes about 30 seconds to spin down. I heard that is a bad thing to do.

I can look at it soon. Can you write a quick draft blurb that would go in the wiki. I want to see how it is presented to the user.

@MitchBradley
Copy link
Collaborator

Do you mean instructions for the rate limit variables?

@bdring
Copy link
Owner

bdring commented Mar 5, 2022

Yes

@bdring
Copy link
Owner

bdring commented Mar 5, 2022

Is the latest code merged to the branch. There are a couple of errors. I don't want to fix them and get out of sync.

@MitchBradley
Copy link
Collaborator

Tell me the errors and I will fix them. After having written the docs (next comment), I thought it best to use percent instead of dev speed, for consistency with speed_map.

@MitchBradley
Copy link
Collaborator

Spindle Ramping

When you ask for a change in spindle speed, FluidNC changes the value of some
hardware signal. For example, the value might be the duty cycle of a PWM
signal. That, in turn controls the electrical power that is applied to the
spindle motor, which speeds up or down according to the new power. (This
does not apply to all spindle types. For RS485 VFD spindles, the target
speed is sent digitally and the smart VFD figures out what to do.)

With default settings, if you ask FluidNC to change the speed from, say,
0 to full speed, FluidNC will change the hardware signal to its full value
in one step, thus letting the external hardware change the spindle speed
as quickly as it can. This is often appropriate, but can sometimes cause
problems. Some spindle motor drivers can change the spindle speed so fast
that it puts undue stress on mechanical or electrical components.

If you need to limit the ramp-up or ramp-down rate of spindle changes, you
can adjust the values of these config items:

  • spinup_max_percent When increasing the spindle speed, increase
    the hardware control signal in steps no larger than this percentage of full
    speed. The default value is 100, meaning always increase the speed in a
    single step.

  • spindown_max_percent When decreasing the spindle speed, decrease
    the hardware control signal in steps no larger than this percentage of full
    speed. The default value is 100, meaning always decrease the speed in a
    single step.

As a concrete example, suppose that spinup_max_percent is 20. If you
increase the speed by 10%, that can be done in one step because 10 is
less than 20. If you increase the speed by 50% of the full speed value,
say from 30% to 80%, it must be done in three steps of at most 20%.
The first step would go from 30% to 50%, the second to 70%, and the
third to the final target of 80%.

The length of time for each step is automatically calculated from
spinup_ms or spindown_ms . If spinup_ms is 2000 ms and
spinup_max_percent is 20, each spinup step takes 400 ms because
20% of 2000 is 400.

In most cases you will not need to use this; just leave spinup_max_percent
and spindown_max_percent at their default values of 100. If you
do need to limit spindle change rate, just reduce their values until your
spindle behaves the way you want. You can adjust the up and down values
separately, as needed.

@bdring
Copy link
Owner

bdring commented Mar 5, 2022

I am good with the implementation

Should this be done in PWM rather than Spindle? I think all of the spindles that can use it are based on PWM. It don't think it should be used on VFDs or On/Off spindles. if (use_delay_settings()) only blocks it from VFDs.

@MitchBradley
Copy link
Collaborator

The core routine spindleRamp is in Spindle so it could, in principle, be called from any spindle type, but it is currently only called from PWM.

I could imagine calling it from DacSpindle too.

@bdring
Copy link
Owner

bdring commented Mar 5, 2022

I am am concerned with the config items being in spindles that can't use them.

I assumed DAC was based on PWM, I agree that spindle could use ramping.

@MitchBradley
Copy link
Collaborator

I will see what I can do about the config item visibility.

@bdring
Copy link
Owner

bdring commented Mar 9, 2022

@TheAntTeam

I think I have a working branch. You can test. Mitch is working on a different approach, so it may change. All you need to do is add use_ramp: true to your spindle.

Can you share your config file? I can test.

https://github.com/bdring/FluidNC/tree/BartsFixedIntervalRamp

https://github.com/bdring/FluidNC/blob/BartsFixedIntervalRamp/FluidNC/src/Spindles/PWM_ramping.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants