#include "modules/communication/GcodeDispatch.h"
#include "modules/robot/Planner.h"
#include "modules/robot/Robot.h"
-#include "modules/robot/Stepper.h"
#include "modules/robot/Conveyor.h"
#include "StepperMotor.h"
#include "BaseSolution.h"
// Core modules
this->add_module( this->gcode_dispatch = new GcodeDispatch() );
this->add_module( this->robot = new Robot() );
- this->add_module( this->stepper = new Stepper() );
this->add_module( this->conveyor = new Conveyor() );
this->add_module( this->simpleshell = new SimpleShell() );
class StreamOutputPool;
class GcodeDispatch;
class Robot;
-class Stepper;
class Planner;
class StepTicker;
class Adc;
StreamOutputPool* streams;
GcodeDispatch* gcode_dispatch;
Robot* robot;
- Stepper* stepper;
Planner* planner;
Config* config;
Conveyor* conveyor;
&Module::on_main_loop,
&Module::on_console_line_received,
&Module::on_gcode_received,
- &Module::on_gcode_execute,
- &Module::on_speed_change,
- &Module::on_block_begin,
- &Module::on_block_end,
&Module::on_idle,
&Module::on_second_tick,
&Module::on_get_public_data,
ON_MAIN_LOOP,
ON_CONSOLE_LINE_RECEIVED,
ON_GCODE_RECEIVED,
- ON_GCODE_EXECUTE,
- ON_SPEED_CHANGE,
- ON_BLOCK_BEGIN,
- ON_BLOCK_END,
ON_IDLE,
ON_SECOND_TICK,
ON_GET_PUBLIC_DATA,
virtual void on_main_loop(void *) {};
virtual void on_console_line_received(void *) {};
virtual void on_gcode_received(void *) {};
- virtual void on_gcode_execute(void *) {};
- virtual void on_speed_change(void *) {};
- virtual void on_block_begin(void *) {};
- virtual void on_block_end(void *) {};
virtual void on_idle(void *) {};
virtual void on_second_tick(void *) {};
virtual void on_get_public_data(void *) {};
Pin* pull_none(void);
- inline bool get(){
+ inline bool get() const{
if (!this->valid) return false;
return this->inverting ^ (( this->port->FIOPIN >> this->pin ) & 1);
}
void start();
bool add_job(Block *block) { return jobq.put(block); }
bool is_jobq_full() const { return jobq.full(); }
+ bool is_jobq_empty() const { return jobq.empty(); }
// whatever setup the block should register this to know when it is done
std::function<void()> finished_fnc{nullptr};
// in steps/sec the default minimum speed (was 20steps/sec hardcoded)
float StepperMotor::default_minimum_actuator_rate= 20.0F;
-// A StepperMotor represents an actual stepper motor. It is used to generate steps that move the actual motor at a given speed
-
-StepperMotor::StepperMotor()
-{
- init();
-}
-
StepperMotor::StepperMotor(Pin &step, Pin &dir, Pin &en) : step_pin(step), dir_pin(dir), en_pin(en)
{
- init();
- enable(false);
set_high_on_debug(en.port_number, en.pin);
-}
-
-StepperMotor::~StepperMotor()
-{
-}
-
-void StepperMotor::init()
-{
// register this motor with the step ticker, and get its index in that array and bit position
this->index= THEKERNEL->step_ticker->register_motor(this);
last_milestone_steps = 0;
last_milestone_mm = 0.0F;
current_position_steps= 0;
+ enable(false);
+
+ this->register_for_event(ON_HALT);
+ this->register_for_event(ON_ENABLE);
+}
+
+StepperMotor::~StepperMotor()
+{
+ THEKERNEL->unregister_for_event(ON_HALT, this);
+ THEKERNEL->unregister_for_event(ON_ENABLE, this);
+}
+
+void StepperMotor::on_halt(void *argument)
+{
+ if(argument == nullptr) {
+ enable(false);
+ }
+}
+
+void StepperMotor::on_enable(void *argument)
+{
+ enable(argument != nullptr);
}
void StepperMotor::change_steps_per_mm(float new_steps)
#pragma once
+#include "Module.h"
#include "Pin.h"
-class StepperMotor {
+class StepperMotor : public Module {
public:
- StepperMotor();
StepperMotor(Pin& step, Pin& dir, Pin& en);
~StepperMotor();
inline void set_direction(bool f) { direction= f; dir_pin.set(f); }
inline void enable(bool state) { en_pin.set(!state); };
+ inline bool is_enabled() const { return !en_pin.get(); };
bool which_direction() const { return direction; }
friend class Robot;
private:
- void init();
+ void on_halt(void *argument);
+ void on_enable(void *argument);
int index;
#include "Conveyor.h"
#include "Gcode.h"
#include "libs/StreamOutputPool.h"
-#include "Stepper.h"
#include "StepTicker.h"
#include "mri.h"
{
//commands.clear();
//travel_distances.clear();
- gcodes.clear();
- std::vector<Gcode>().swap(gcodes); // this resizes the vector releasing its memory
+ //gcodes.clear();
+ //std::vector<Gcode>().swap(gcodes); // this resizes the vector releasing its memory
this->steps.fill(0);
nominal_length_flag = false;
max_entry_speed = 0.0F;
is_ready = false;
- times_taken = 0;
+ is_job = false;
+
acceleration_per_tick= 0;
deceleration_per_tick= 0;
total_move_ticks= 0;
void Block::debug()
{
- THEKERNEL->streams->printf("%p: steps:X%04lu Y%04lu Z%04lu(max:%4lu) nominal:r%6.1f/s%6.1f mm:%9.6f acc:%5lu dec:%5lu rates:%10.4f entry/max: %10.4f/%10.4f taken:%d ready:%d recalc:%d nomlen:%d\r\n",
+ THEKERNEL->streams->printf("%p: steps:X%04lu Y%04lu Z%04lu(max:%4lu) nominal:r%6.1f/s%6.1f mm:%9.6f acc:%5lu dec:%5lu rates:%10.4f entry/max: %10.4f/%10.4f ready:%d is_job:%d recalc:%d nomlen:%d\r\n",
this,
this->steps[0],
this->steps[1],
this->initial_rate,
this->entry_speed,
this->max_entry_speed,
- this->times_taken,
this->is_ready,
+ this->is_job,
recalculate_flag ? 1 : 0,
nominal_length_flag ? 1 : 0
);
void Block::calculate_trapezoid( float entryspeed, float exitspeed )
{
// if block is currently executing, don't touch anything!
- if (times_taken)
- return;
+ if(is_job) return;
float initial_rate = this->nominal_rate * (entryspeed / this->nominal_speed); // steps/sec
float final_rate = this->nominal_rate * (exitspeed / this->nominal_speed);
{
// if block is currently executing, return cached exit speed from calculate_trapezoid
// this ensures that a block following a currently executing block will have correct entry speed
- if (times_taken)
- return exit_speed;
+ // FIXME
+ // if (times_taken)
+ // return exit_speed;
// if nominal_length_flag is asserted
// we are guaranteed to reach nominal speed regardless of entry speed
}
// Gcodes are attached to their respective blocks so that on_gcode_execute can be called with it
-void Block::append_gcode(Gcode* gcode)
-{
- Gcode new_gcode = *gcode;
- new_gcode.strip_parameters(); // optimization to save memory we strip off the XYZIJK parameters from the saved command
- gcodes.push_back(new_gcode);
-}
+// void Block::append_gcode(Gcode* gcode)
+// {
+// Gcode new_gcode = *gcode;
+// new_gcode.strip_parameters(); // optimization to save memory we strip off the XYZIJK parameters from the saved command
+// gcodes.push_back(new_gcode);
+// }
void Block::begin()
{
+ // can no longer be used in planning
recalculate_flag = false;
+ is_job= true; // mark as being executed or qued for execution
+ // TODO probably should remove this
if (!is_ready)
__debugbreak();
- times_taken = -1;
-
// execute all the gcodes related to this block
- for(unsigned int index = 0; index < gcodes.size(); index++)
- THEKERNEL->call_event(ON_GCODE_EXECUTE, &(gcodes[index]));
-
+ // for(unsigned int index = 0; index < gcodes.size(); index++)
+ // THEKERNEL->call_event(ON_GCODE_EXECUTE, &(gcodes[index]));
- THEKERNEL->call_event(ON_BLOCK_BEGIN, this);
-
- if (times_taken < 0)
- release();
+ THEKERNEL->conveyor->on_block_begin(this);
}
// Signal the conveyor that this block is ready to be injected into the system
this->is_ready = true;
}
-// Mark the block as taken by one more module
-void Block::take()
-{
- if (times_taken < 0)
- times_taken = 0;
- times_taken++;
-}
-
-// Mark the block as no longer taken by one module, go to next block if this frees it
+// Mark the block as finished
void Block::release()
{
- if (--this->times_taken <= 0) {
- times_taken = 0;
- if (is_ready) {
- is_ready = false;
- THEKERNEL->call_event(ON_BLOCK_END, this);
-
- // ensure conveyor gets called last
- THEKERNEL->conveyor->on_block_end(this);
- }
+ if (is_ready) {
+ is_ready = false;
+ THEKERNEL->conveyor->on_block_end(this);
}
}
void debug();
- void append_gcode(Gcode* gcode);
+ // void append_gcode(Gcode* gcode);
- void take();
void release();
void ready();
void begin();
- std::vector<Gcode> gcodes;
+ //std::vector<Gcode> gcodes;
std::array<uint32_t, k_max_actuators> steps; // Number of steps for each axis for this block
uint32_t steps_event_count; // Steps for the longest axis
float max_entry_speed;
- int16_t times_taken; // A block can be "taken" by any number of modules, and the next block is not moved to until all the modules have "released" it. This value serves as a tracker.
-
std::bitset<k_max_actuators> direction_bits; // Direction for each axis in bit form, relative to the direction port's mask
struct {
bool recalculate_flag:1; // Planner flag to recalculate trapezoids on entry junction
bool nominal_length_flag:1; // Planner flag for nominal speed always reached
bool is_ready:1;
+ bool is_job:1; // set when queued to run in the job queue
};
};
You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
*/
-using namespace std;
-#include <vector>
#include "libs/nuts_bolts.h"
#include "libs/RingBuffer.h"
#include "../communication/utils/Gcode.h"
#include "Config.h"
#include "libs/StreamOutputPool.h"
#include "ConfigValue.h"
+#include "StepTicker.h"
+
+#include <functional>
+#include <vector>
#define planner_queue_size_checksum CHECKSUM("planner_queue_size")
* Thus, our two ringbuffers exist sharing the one ring of blocks, and we safely marshall used blocks from ISR context to IDLE context for safe cleanup.
*/
-Conveyor::Conveyor(){
+
+/*
+
+A Block is created by the planner in Planner::append_block() and stuck on the
+head of the queue.
+
+The Conveyor is always checking (in on_idle) when blocks on the queue become
+fully planned (ie recalculate flag gets cleared). When this happens a pointer
+to the block is pushed onto the job queue in stepticker, but also left in the
+block queue. If the job queue is full then it will get pushed when the job
+queue has room.
+
+Once the job has finished being executed the block is
+removed from the block queue (at least marked to be removed as explained
+above).
+
+If the block queue has entries that are not yet fully planned, and a certain
+time has elapsed it gives up waiting and forces the tail of the block queue
+onto the job queue, the time needs to be configurable, but it needs to be long
+enough to allow gcodes to be sent from a host, or read from sdcard, so that
+even very short moves will get some planning, otherwise a stream of very short
+moves will be very jerky as they will always decelerate to zero. However the
+delay cannot be so long that there is a noticable lag for jog commands.
+
+TODO an optimization is to remove the block from the block queue and put it
+into the job queue, as this happens in on_idle it should be safe to do without
+the double pointer stuff. as the job queue has nothing that needs deleting
+this works out.
+
+*/
+
+Conveyor::Conveyor()
+{
gc_pending = queue.tail_i;
running = false;
flush = false;
- halted= false;
+ halted = false;
}
-void Conveyor::on_module_loaded(){
+void Conveyor::on_module_loaded()
+{
register_for_event(ON_IDLE);
register_for_event(ON_MAIN_LOOP);
register_for_event(ON_HALT);
- on_config_reload(this);
+ // Attach to the end_of_move stepper event
+ THEKERNEL->step_ticker->finished_fnc = std::bind( &Conveyor::all_moves_finished, this);
+ queue.resize(THEKERNEL->config->value(planner_queue_size_checksum)->by_default(32)->as_number());
}
-void Conveyor::on_halt(void* argument){
+void Conveyor::on_halt(void* argument)
+{
if(argument == nullptr) {
- halted= true;
+ halted = true;
flush_queue();
- }else{
- halted= false;
+ } else {
+ halted = false;
}
}
// Delete blocks here, because they can't be deleted in interrupt context ( see Block.cpp:release )
// note that blocks get cleaned as they come off the tail, so head ALWAYS points to a cleaned block.
-void Conveyor::on_idle(void* argument){
- if (queue.tail_i != gc_pending)
- {
+void Conveyor::on_idle(void* argument)
+{
+ if (queue.tail_i != gc_pending) {
if (queue.is_empty()) {
__debugbreak();
- }else{
+ } else {
// Cleanly delete block
Block* block = queue.tail_ref();
// block->debug();
void Conveyor::on_main_loop(void*)
{
- if (running)
- return;
+ if (running) {
+ if(THEKERNEL->step_ticker->is_jobq_full()) return;
- if (queue.is_empty())
- {
- if (queue.head_ref()->gcodes.size())
- {
- queue_head_block();
- ensure_running();
- }
+ // see if there are any fully planned things on the queue we can give to step ticker
+ // TODO....
+
+ return;
}
- else
- // queue not empty
+
+ // not currently running, see if we can find something to do
+ if (queue.is_empty()) {
+ // nothing to do
+
+ // if (queue.head_ref()->gcodes.size())
+ // {
+ // queue_head_block();
+ // ensure_running();
+ // }
+ } else {
+ // queue not empty so see if we can stick something on the stepticker job queue
+ // we have to walk back and find blocks where recalculate_flag is clear.
+ // otherwise if the jobq is empty... (don't force anyhting if we have at least one thing on the job queue)
+ // see if we have reached the time limit, otherwise give it some more time to finish planning some entries
+ // if we have reached the time limit force the next thing on the queue into the jobq, fully planned or not
ensure_running();
+ }
}
-void Conveyor::on_config_reload(void* argument)
+// void Conveyor::append_gcode(Gcode* gcode)
+// {
+// queue.head_ref()->append_gcode(gcode);
+// }
+
+// When all moves in a block have finished this is called by step ticker (in the pendsv ISR)
+void Conveyor::all_moves_finished()
{
- queue.resize(THEKERNEL->config->value(planner_queue_size_checksum)->by_default(32)->as_number());
+ // TODO ideally we should pass in the pointer to the block that just completed, but stepticker doesn't really know what it is,
+ // but it has to be the tail of the block queue
+ // we mark the block as deleted here (release it), which will call on_block_end() below
+ this->queue.item_ref(gc_pending)->release();
}
-void Conveyor::append_gcode(Gcode* gcode)
+// A new block is popped from the queue
+void Conveyor::on_block_begin(Block *block)
{
- queue.head_ref()->append_gcode(gcode);
+ // setup stepticker to execute this block
+ // if it returns false the job queue was full
+ THEKERNEL->step_ticker->add_job(block);
}
// Process a new block in the queue
-void Conveyor::on_block_end(void* block)
+// gets called when the block is released
+void Conveyor::on_block_end(Block *block)
{
if (queue.is_empty())
__debugbreak();
gc_pending = queue.next(gc_pending);
// mark entire queue for GC if flush flag is asserted
- if (flush){
+ if (flush) {
while (gc_pending != queue.head_i) {
gc_pending = queue.next(gc_pending);
}
}
// Return if queue is empty
- if (gc_pending == queue.head_i)
- {
+ if (gc_pending == queue.head_i) {
running = false;
return;
}
- // Get a new block
- Block* next = this->queue.item_ref(gc_pending);
+// // Get a new block
+// Block* next = this->queue.item_ref(gc_pending);
+// next->begin(); // causes on_block_begin() (above) to be called
- next->begin();
}
// Wait for the queue to be empty
// clear and release the block on the head
queue.head_ref()->clear();
- }else{
+ } else {
queue.head_ref()->ready();
queue.produce_head();
}
void Conveyor::ensure_running()
{
- if (!running)
- {
- if (gc_pending == queue.head_i)
- return;
+ // if we are already running or the block queue is empty do nothing
+ if (running || gc_pending == queue.head_i) return;
+ // if the job queue is empty we can force the next block into it
+ if(THEKERNEL->step_ticker->is_jobq_empty()) {
+ // force the next block if not already forced
+ if(queue.item_ref(gc_pending)->is_job) return;
running = true;
- queue.item_ref(gc_pending)->begin();
+ queue.item_ref(gc_pending)->begin(); // on_block_begin() above gets called
}
}
// Debug function
void Conveyor::dump_queue()
{
- for (unsigned int index = queue.tail_i, i = 0; true; index = queue.next(index), i++ )
- {
+ for (unsigned int index = queue.tail_i, i = 0; true; index = queue.next(index), i++ ) {
THEKERNEL->streams->printf("block %03d > ", i);
queue.item_ref(index)->debug();
void on_module_loaded(void);
void on_idle(void *);
void on_main_loop(void *);
- void on_block_end(void *);
+ void on_block_end(Block *);
+ void on_block_begin(Block *);
void on_halt(void *);
- void on_config_reload(void *);
-
- void notify_block_finished(Block *);
void wait_for_empty_queue();
bool is_queue_empty() { return queue.is_empty(); };
bool is_queue_full() { return queue.is_full(); };
- void ensure_running(void);
- void append_gcode(Gcode *);
+ //void append_gcode(Gcode *);
void queue_head_block(void);
void dump_queue(void);
friend class Planner; // for queue
private:
- typedef HeapRing<Block> Queue_t;
+ void all_moves_finished();
+ void ensure_running(void);
+ using Queue_t= HeapRing<Block>;
Queue_t queue; // Queue of Blocks
volatile unsigned int gc_pending;
#include "Config.h"
#include "checksumm.h"
#include "Robot.h"
-#include "Stepper.h"
#include "ConfigValue.h"
#include <math.h>
current_wcs = 0;
absolute_mode = true;
break;
+ case 17:
+ THEKERNEL->call_event(ON_ENABLE, (void*)1); // turn all enable pins on
+ break;
+
+ case 18: // this used to support parameters, now it ignores them
+ case 84:
+ THEKERNEL->conveyor->wait_for_empty_queue();
+ THEKERNEL->call_event(ON_ENABLE, nullptr); // turn all enable pins off
+ break;
case 92: // M92 - set steps per mm
if (gcode->has_letter('X'))
void Robot::distance_in_gcode_is_known(Gcode * gcode)
{
//If the queue is empty, execute immediately, otherwise attach to the last added block
- THEKERNEL->conveyor->append_gcode(gcode);
+ //THEKERNEL->conveyor->append_gcode(gcode);
}
// reset the machine position for all axis. Used for homing.
this->next_command_is_MCS = false; // always reset this
- if(moved) {
- // if adding these blocks didn't start executing, do that now
- THEKERNEL->conveyor->ensure_running();
- }
+ // this is not neede as COnveyor::on_main_loop will do something
+ // if(moved) {
+ // // if adding these blocks didn't start executing, do that now
+ // THEKERNEL->conveyor->ensure_running();
+ // }
return moved;
}
+++ /dev/null
-/*
- 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)
- 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.
- 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.
- You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#include "Stepper.h"
-
-#include "libs/Module.h"
-#include "libs/Kernel.h"
-#include "Planner.h"
-#include "Conveyor.h"
-#include "StepperMotor.h"
-#include "Robot.h"
-#include "checksumm.h"
-#include "SlowTicker.h"
-#include "Config.h"
-#include "ConfigValue.h"
-#include "Gcode.h"
-#include "Block.h"
-#include "StepTicker.h"
-
-#include <functional>
-
-#include "libs/nuts_bolts.h"
-#include "libs/Hook.h"
-
-#include <mri.h>
-
-// The stepper reacts to blocks that have XYZ movement to transform them into actual stepper motor moves
-Stepper::Stepper()
-{
- this->current_block = NULL;
-}
-
-//Called when the module has just been loaded
-void Stepper::on_module_loaded()
-{
- this->register_for_event(ON_BLOCK_BEGIN);
- this->register_for_event(ON_BLOCK_END);
- this->register_for_event(ON_GCODE_RECEIVED);
- this->register_for_event(ON_HALT);
-
- // Get onfiguration
- this->on_config_reload(this);
-
- // Attach to the end_of_move stepper event
- THEKERNEL->step_ticker->finished_fnc= std::bind( &Stepper::stepper_motor_finished_move, this);
-}
-
-// Get configuration from the config file
-void Stepper::on_config_reload(void *argument)
-{
- // Steppers start off by default
- this->turn_enable_pins_off();
-}
-
-void Stepper::on_halt(void *argument)
-{
- if(argument == nullptr) {
- this->turn_enable_pins_off();
- }
-}
-
-void Stepper::on_gcode_received(void *argument)
-{
- Gcode *gcode = static_cast<Gcode *>(argument);
-
- if( gcode->has_m) {
- if( gcode->m == 17 ) {
- this->turn_enable_pins_on();
-
- }else if( (gcode->m == 84 || gcode->m == 18) && !gcode->has_letter('E') ) {
- THEKERNEL->conveyor->wait_for_empty_queue();
- this->turn_enable_pins_off();
- }
- }
-}
-
-// Enable steppers
-void Stepper::turn_enable_pins_on()
-{
- for (auto a : THEKERNEL->robot->actuators)
- a->enable(true);
- this->enable_pins_status = true;
- THEKERNEL->call_event(ON_ENABLE, (void*)1);
-}
-
-// Disable steppers
-void Stepper::turn_enable_pins_off()
-{
- for (auto a : THEKERNEL->robot->actuators)
- a->enable(false);
- this->enable_pins_status = false;
- THEKERNEL->call_event(ON_ENABLE, nullptr);
-}
-
-// A new block is popped from the queue
-void Stepper::on_block_begin(void *argument)
-{
- Block *block = static_cast<Block *>(argument);
-
- // Mark the new block as of interrest to us, handle blocks that have no axis moves properly (like Extrude blocks etc)
- bool take = false;
- if (block->millimeters > 0.0F) {
- for (size_t s = 0; !take && s < THEKERNEL->robot->actuators.size(); s++) {
- take = block->steps[s] > 0;
- }
- }
- if(!take) return;
-
- block->take();
-
- // We can't move with the enable pins off
- if( this->enable_pins_status == false ) {
- this->turn_enable_pins_on();
- }
-
- this->current_block = block;
-
- // setup stepticker to execute this block
- THEKERNEL->step_ticker->add_job(block);
-}
-
-// Current block is discarded
-void Stepper::on_block_end(void *argument)
-{
- this->current_block = NULL; //stfu !
-}
-
-// When all moves in a block have finished this is called by step ticker (in the pendsv ISR)
-void Stepper::stepper_motor_finished_move()
-{
- // This block is finished, release it
- if( this->current_block != NULL ) {
- this->current_block->release();
- }
-}
+++ /dev/null
-/*
- This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
- 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.
- 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.
- You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef STEPPER_H
-#define STEPPER_H
-
-#include "libs/Module.h"
-#include <stdint.h>
-
-class Block;
-class StepperMotor;
-
-class Stepper : public Module
-{
-public:
- Stepper();
- void on_module_loaded();
- void on_config_reload(void *argument);
- void on_block_begin(void *argument);
- void on_block_end(void *argument);
- void on_gcode_received(void *argument);
- void on_halt(void *argument);
-
- void turn_enable_pins_on();
- void turn_enable_pins_off();
-
- const Block *get_current_block() const { return current_block; }
-
-private:
- void stepper_motor_finished_move();
-
- Block *current_block;
-
- struct {
- bool enable_pins_status:1;
- };
-
-};
-
-
-
-
-#endif