improve handling of flush queue in extruder
[clinton/Smoothieware.git] / src / modules / robot / Stepper.cpp
CommitLineData
7b49793d 1/*
5886a464 2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl) with additions from Sungeun K. Jeon (https://github.com/chamnit/grbl)
4cff3ded
AW
3 Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
4 Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
7b49793d 5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
4cff3ded
AW
6*/
7
5673fe39
MM
8#include "Stepper.h"
9
4cff3ded
AW
10#include "libs/Module.h"
11#include "libs/Kernel.h"
4cff3ded 12#include "Planner.h"
3fceb8eb 13#include "Conveyor.h"
5673fe39 14#include "StepperMotor.h"
61134a65
JM
15#include "Robot.h"
16#include "checksumm.h"
17#include "SlowTicker.h"
18#include "Config.h"
8d54c34c 19#include "ConfigValue.h"
66383b80 20#include "Gcode.h"
558e170c 21#include "Block.h"
5673fe39 22
4cff3ded
AW
23#include <vector>
24using namespace std;
5673fe39 25
4cff3ded 26#include "libs/nuts_bolts.h"
2f7d3dba 27#include "libs/Hook.h"
5673fe39 28
db453125
AW
29#include <mri.h>
30
38bf9a1c
JM
31#define acceleration_ticks_per_second_checksum CHECKSUM("acceleration_ticks_per_second")
32#define minimum_steps_per_minute_checksum CHECKSUM("minimum_steps_per_minute")
db453125 33
edac9072 34// The stepper reacts to blocks that have XYZ movement to transform them into actual stepper motor moves
d337942a 35// TODO: This does accel, accel should be in StepperMotor
edac9072 36
b375ba1d
JM
37Stepper::Stepper()
38{
4cff3ded 39 this->current_block = NULL;
81b547a1 40 this->paused = false;
650ed0a8 41 this->trapezoid_generator_busy = false;
1013f6a3 42 this->force_speed_update = false;
728477c4 43 this->halted= false;
4cff3ded
AW
44}
45
46//Called when the module has just been loaded
b375ba1d
JM
47void Stepper::on_module_loaded()
48{
3a4fa0c1
AW
49 this->register_for_event(ON_BLOCK_BEGIN);
50 this->register_for_event(ON_BLOCK_END);
6b833f7d 51 this->register_for_event(ON_GCODE_EXECUTE);
31e600f6 52 this->register_for_event(ON_GCODE_RECEIVED);
befcf5cc
AW
53 this->register_for_event(ON_PLAY);
54 this->register_for_event(ON_PAUSE);
3d1a4519 55 this->register_for_event(ON_HALT);
7b49793d 56
4cff3ded 57 // Get onfiguration
7b49793d 58 this->on_config_reload(this);
4cff3ded 59
ded56b35 60 // Acceleration ticker
314ab8f7 61 this->acceleration_tick_hook = THEKERNEL->slow_ticker->attach( this->acceleration_ticks_per_second, this, &Stepper::trapezoid_generator_tick );
4cff3ded 62
feb204be 63 // Attach to the end_of_move stepper event
314ab8f7
MM
64 THEKERNEL->robot->alpha_stepper_motor->attach(this, &Stepper::stepper_motor_finished_move );
65 THEKERNEL->robot->beta_stepper_motor->attach( this, &Stepper::stepper_motor_finished_move );
66 THEKERNEL->robot->gamma_stepper_motor->attach(this, &Stepper::stepper_motor_finished_move );
4cff3ded
AW
67}
68
2bb8b390 69// Get configuration from the config file
b375ba1d
JM
70void Stepper::on_config_reload(void *argument)
71{
7b49793d 72
314ab8f7 73 this->acceleration_ticks_per_second = THEKERNEL->config->value(acceleration_ticks_per_second_checksum)->by_default(100 )->as_number();
755beef1 74 this->minimum_steps_per_second = THEKERNEL->config->value(minimum_steps_per_minute_checksum )->by_default(120 )->as_number() / 60.0F;
bee725fc 75
5a884140
MM
76 // Steppers start off by default
77 this->turn_enable_pins_off();
da24d6ae
AW
78}
79
befcf5cc 80// When the play/pause button is set to pause, or a module calls the ON_PAUSE event
b375ba1d
JM
81void Stepper::on_pause(void *argument)
82{
81b547a1 83 this->paused = true;
314ab8f7
MM
84 THEKERNEL->robot->alpha_stepper_motor->pause();
85 THEKERNEL->robot->beta_stepper_motor->pause();
86 THEKERNEL->robot->gamma_stepper_motor->pause();
befcf5cc
AW
87}
88
89// When the play/pause button is set to play, or a module calls the ON_PLAY event
b375ba1d
JM
90void Stepper::on_play(void *argument)
91{
7b49793d 92 // TODO: Re-compute the whole queue for a cold-start
81b547a1 93 this->paused = false;
314ab8f7
MM
94 THEKERNEL->robot->alpha_stepper_motor->unpause();
95 THEKERNEL->robot->beta_stepper_motor->unpause();
96 THEKERNEL->robot->gamma_stepper_motor->unpause();
befcf5cc
AW
97}
98
b375ba1d 99void Stepper::on_halt(void *argument)
3d1a4519 100{
728477c4
JM
101 if(argument == nullptr) {
102 this->turn_enable_pins_off();
103 this->halted= true;
104 }else{
105 this->halted= false;
106 }
3d1a4519
JM
107}
108
b375ba1d
JM
109void Stepper::on_gcode_received(void *argument)
110{
111 Gcode *gcode = static_cast<Gcode *>(argument);
31e600f6
JM
112 // Attach gcodes to the last block for on_gcode_execute
113 if( gcode->has_m && (gcode->m == 84 || gcode->m == 17 || gcode->m == 18 )) {
e0ee24ed 114 THEKERNEL->conveyor->append_gcode(gcode);
728477c4 115
1cf31736 116 }
31e600f6
JM
117}
118
edac9072 119// React to enable/disable gcodes
b375ba1d
JM
120void Stepper::on_gcode_execute(void *argument)
121{
122 Gcode *gcode = static_cast<Gcode *>(argument);
6b833f7d 123
b375ba1d
JM
124 if( gcode->has_m) {
125 if( gcode->m == 17 ) {
7b49793d 126 this->turn_enable_pins_on();
831bade1 127 }
b375ba1d 128 if( (gcode->m == 84 || gcode->m == 18) && !gcode->has_letter('E') ) {
7b49793d 129 this->turn_enable_pins_off();
6b833f7d
MM
130 }
131 }
132}
133
edac9072 134// Enable steppers
b375ba1d
JM
135void Stepper::turn_enable_pins_on()
136{
137 for (StepperMotor *m : THEKERNEL->robot->actuators)
9c5fa39a 138 m->enable(true);
831bade1
AW
139 this->enable_pins_status = true;
140}
141
edac9072 142// Disable steppers
b375ba1d
JM
143void Stepper::turn_enable_pins_off()
144{
145 for (StepperMotor *m : THEKERNEL->robot->actuators)
9c5fa39a 146 m->enable(false);
831bade1
AW
147 this->enable_pins_status = false;
148}
149
7b49793d 150// A new block is popped from the queue
b375ba1d
JM
151void Stepper::on_block_begin(void *argument)
152{
153 Block *block = static_cast<Block *>(argument);
3a4fa0c1
AW
154
155 // The stepper does not care about 0-blocks
b375ba1d
JM
156 if( block->millimeters == 0.0F ) {
157 return;
158 }
7b49793d 159
4464301d 160 // Mark the new block as of interrest to us
b375ba1d 161 if( block->steps[ALPHA_STEPPER] > 0 || block->steps[BETA_STEPPER] > 0 || block->steps[GAMMA_STEPPER] > 0 ) {
4464301d 162 block->take();
b375ba1d 163 } else {
4464301d
AW
164 return;
165 }
813727fb 166
7b49793d 167 // We can't move with the enable pins off
b375ba1d 168 if( this->enable_pins_status == false ) {
831bade1
AW
169 this->turn_enable_pins_on();
170 }
171
7b49793d 172 // Setup : instruct stepper motors to move
b375ba1d 173 if( block->steps[ALPHA_STEPPER] > 0 ) {
f3b48426 174 THEKERNEL->robot->alpha_stepper_motor->move( block->direction_bits[ALPHA_STEPPER], block->steps[ALPHA_STEPPER]);
b375ba1d
JM
175 }
176 if( block->steps[BETA_STEPPER ] > 0 ) {
f3b48426 177 THEKERNEL->robot->beta_stepper_motor->move( block->direction_bits[BETA_STEPPER], block->steps[BETA_STEPPER ]);
b375ba1d
JM
178 }
179 if( block->steps[GAMMA_STEPPER] > 0 ) {
f3b48426 180 THEKERNEL->robot->gamma_stepper_motor->move( block->direction_bits[GAMMA_STEPPER], block->steps[GAMMA_STEPPER]);
b375ba1d 181 }
3a4fa0c1 182
1a2d88eb 183 this->current_block = block;
feb204be 184
7b49793d 185 // Setup acceleration for this block
1a2d88eb 186 this->trapezoid_generator_reset();
ded56b35 187
feb204be 188 // Find the stepper with the more steps, it's the one the speed calculations will want to follow
314ab8f7 189 this->main_stepper = THEKERNEL->robot->alpha_stepper_motor;
b375ba1d
JM
190 if( THEKERNEL->robot->beta_stepper_motor->steps_to_move > this->main_stepper->steps_to_move ) {
191 this->main_stepper = THEKERNEL->robot->beta_stepper_motor;
192 }
193 if( THEKERNEL->robot->gamma_stepper_motor->steps_to_move > this->main_stepper->steps_to_move ) {
194 this->main_stepper = THEKERNEL->robot->gamma_stepper_motor;
195 }
feb204be 196
edac9072 197 // Set the initial speed for this move
9883efb8
AW
198 this->trapezoid_generator_tick(0);
199
2f7d3dba 200 // Synchronise the acceleration curve with the stepping
658b8a40 201 this->synchronize_acceleration(0);
2f7d3dba 202
3a4fa0c1
AW
203}
204
205// Current block is discarded
b375ba1d
JM
206void Stepper::on_block_end(void *argument)
207{
813727fb 208 this->current_block = NULL; //stfu !
3a4fa0c1 209}
da24d6ae 210
feb204be 211// When a stepper motor has finished it's assigned movement
b375ba1d
JM
212uint32_t Stepper::stepper_motor_finished_move(uint32_t dummy)
213{
81b547a1 214
feb204be 215 // We care only if none is still moving
b375ba1d
JM
216 if( THEKERNEL->robot->alpha_stepper_motor->moving || THEKERNEL->robot->beta_stepper_motor->moving || THEKERNEL->robot->gamma_stepper_motor->moving ) {
217 return 0;
218 }
7b49793d 219
feb204be 220 // This block is finished, release it
b375ba1d 221 if( this->current_block != NULL ) {
7b49793d 222 this->current_block->release();
4cff3ded 223 }
7b49793d 224
9e672116 225 return 0;
4cff3ded
AW
226}
227
83ecfc46 228
4cff3ded
AW
229// This is called ACCELERATION_TICKS_PER_SECOND times per second by the step_event
230// interrupt. It can be assumed that the trapezoid-generator-parameters and the
231// current_block stays untouched by outside handlers for the duration of this function call.
b375ba1d
JM
232uint32_t Stepper::trapezoid_generator_tick( uint32_t dummy )
233{
1013f6a3 234
edac9072 235 // Do not do the accel math for nothing
813727fb 236 if(this->current_block && !this->paused && this->main_stepper->moving ) {
1cf31736
JM
237
238 // Store this here because we use it a lot down there
813727fb 239 uint32_t current_steps_completed = this->main_stepper->stepped;
7b49793d 240
b375ba1d
JM
241 if( this->force_speed_update ) {
242 // Do not accel, just set the value
243 this->force_speed_update = false;
244
245 } else if(THEKERNEL->conveyor->is_flushing()) {
246 // if we are flushing the queue, decelerate to 0 then finish this block
247 if (trapezoid_adjusted_rate > current_block->rate_delta * 1.5F) {
248 trapezoid_adjusted_rate -= current_block->rate_delta;
249
250 } else if (trapezoid_adjusted_rate == current_block->rate_delta * 0.5F) {
c1c802b1
JM
251 for (auto i : THEKERNEL->robot->actuators) i->move(i->direction, 0); // stop motors
252 if (current_block) current_block->release();
253 THEKERNEL->call_event(ON_SPEED_CHANGE, 0); // tell others we stopped
b375ba1d 254 return 0;
c1c802b1 255
b375ba1d
JM
256 } else {
257 trapezoid_adjusted_rate = current_block->rate_delta * 0.5F;
258 }
259
f3b48426 260 } else if(current_steps_completed <= this->current_block->accelerate_until+1) {
b375ba1d 261 // If we are accelerating
1cf31736 262 // Increase speed
edac9072 263 this->trapezoid_adjusted_rate += this->current_block->rate_delta;
b375ba1d
JM
264 if (this->trapezoid_adjusted_rate > this->current_block->nominal_rate ) {
265 this->trapezoid_adjusted_rate = this->current_block->nominal_rate;
266 }
267
f3b48426 268 } else if (current_steps_completed > this->current_block->decelerate_after) {
b375ba1d
JM
269 // If we are decelerating
270 // Reduce speed
271 // NOTE: We will only reduce speed if the result will be > 0. This catches small
272 // rounding errors that might leave steps hanging after the last trapezoid tick.
273 if(this->trapezoid_adjusted_rate > this->current_block->rate_delta * 1.5F) {
274 this->trapezoid_adjusted_rate -= this->current_block->rate_delta;
275 } else {
276 this->trapezoid_adjusted_rate = this->current_block->rate_delta * 1.5F;
277 }
278 if(this->trapezoid_adjusted_rate < this->current_block->final_rate ) {
279 this->trapezoid_adjusted_rate = this->current_block->final_rate;
280 }
281
282 } else if (trapezoid_adjusted_rate != current_block->nominal_rate) {
283 // If we are cruising
284 // Make sure we cruise at exactly nominal rate
285 this->trapezoid_adjusted_rate = this->current_block->nominal_rate;
286 }
287
288 this->set_step_events_per_second(this->trapezoid_adjusted_rate);
ded56b35 289 }
1013f6a3 290
b852a30c 291 return 0;
4cff3ded
AW
292}
293
4cff3ded
AW
294// Initializes the trapezoid generator from the current block. Called whenever a new
295// block begins.
b375ba1d
JM
296inline void Stepper::trapezoid_generator_reset()
297{
4cff3ded 298 this->trapezoid_adjusted_rate = this->current_block->initial_rate;
1013f6a3 299 this->force_speed_update = true;
4cff3ded
AW
300}
301
feb204be 302// Update the speed for all steppers
da947c62
MM
303void Stepper::set_step_events_per_second( float steps_per_second )
304{
c58d32a8 305 // We do not step slower than this, FIXME shoul dbe calculated for the slowest axis not the fastest
0ac1713f 306 //steps_per_second = max(steps_per_second, this->minimum_steps_per_second);
b375ba1d 307 if( steps_per_second < this->minimum_steps_per_second ) {
da947c62 308 steps_per_second = this->minimum_steps_per_second;
813727fb 309 }
4cff3ded 310
feb204be 311 // Instruct the stepper motors
b375ba1d
JM
312 if( THEKERNEL->robot->alpha_stepper_motor->moving ) {
313 THEKERNEL->robot->alpha_stepper_motor->set_speed( steps_per_second * ( (float)this->current_block->steps[ALPHA_STEPPER] / (float)this->current_block->steps_event_count ) );
314 }
315 if( THEKERNEL->robot->beta_stepper_motor->moving ) {
316 THEKERNEL->robot->beta_stepper_motor->set_speed( steps_per_second * ( (float)this->current_block->steps[BETA_STEPPER ] / (float)this->current_block->steps_event_count ) );
317 }
318 if( THEKERNEL->robot->gamma_stepper_motor->moving ) {
319 THEKERNEL->robot->gamma_stepper_motor->set_speed( steps_per_second * ( (float)this->current_block->steps[GAMMA_STEPPER] / (float)this->current_block->steps_event_count ) );
320 }
4cff3ded 321
edac9072 322 // Other modules might want to know the speed changed
314ab8f7 323 THEKERNEL->call_event(ON_SPEED_CHANGE, this);
4464301d 324
4cff3ded
AW
325}
326
7b49793d 327// This function has the role of making sure acceleration and deceleration curves have their
0ac1713f 328// rhythm synchronized. The accel/decel must start at the same moment as the speed update routine
2f7d3dba
AW
329// This is caller in "step just occured" or "block just began" ( step Timer ) context, so we need to be fast.
330// All we do is reset the other timer so that it does what we want
b375ba1d
JM
331uint32_t Stepper::synchronize_acceleration(uint32_t dummy)
332{
2f7d3dba 333
2f7d3dba
AW
334 // No move was done, this is called from on_block_begin
335 // This means we setup the accel timer in a way where it gets called right after
7b49793d 336 // we exit this step interrupt, and so that it is then in synch with
b375ba1d 337 if( this->main_stepper->stepped == 0 ) {
2f7d3dba
AW
338 // Whatever happens, we must call the accel interrupt asap
339 // Because it will set the initial rate
340 // We also want to synchronize in case we start accelerating or decelerating now
7b49793d
MM
341
342 // Accel interrupt must happen asap
2f7d3dba
AW
343 NVIC_SetPendingIRQ(TIMER2_IRQn);
344 // Synchronize both counters
345 LPC_TIM2->TC = LPC_TIM0->TC;
7b49793d
MM
346
347 // If we start decelerating after this, we must ask the actuator to warn us
2f7d3dba 348 // so we can do what we do in the "else" bellow
b375ba1d 349 if( this->current_block->decelerate_after > 0 && this->current_block->decelerate_after < this->main_stepper->steps_to_move ) {
2f7d3dba 350 this->main_stepper->attach_signal_step(this->current_block->decelerate_after, this, &Stepper::synchronize_acceleration);
7b49793d 351 }
b375ba1d 352 } else {
2f7d3dba 353 // If we are called not at the first steps, this means we are beginning deceleration
7b49793d 354 NVIC_SetPendingIRQ(TIMER2_IRQn);
2f7d3dba 355 // Synchronize both counters
7b49793d 356 LPC_TIM2->TC = LPC_TIM0->TC;
2f7d3dba
AW
357 }
358
658b8a40 359 return 0;
2f7d3dba
AW
360}
361