Better and correct fix for Kill hanging on E moving
[clinton/Smoothieware.git] / src / modules / robot / Conveyor.cpp
index 7055817..2d0ce2e 100644 (file)
@@ -5,8 +5,6 @@
       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"
@@ -18,133 +16,251 @@ using namespace std;
 #include "Conveyor.h"
 #include "Planner.h"
 #include "mri.h"
+#include "checksumm.h"
+#include "Config.h"
+#include "libs/StreamOutputPool.h"
+#include "ConfigValue.h"
+#include "StepTicker.h"
+#include "Robot.h"
+#include "StepperMotor.h"
+
+#include <functional>
+#include <vector>
+
+#include "mbed.h"
 
 #define planner_queue_size_checksum CHECKSUM("planner_queue_size")
+#define queue_delay_time_ms_checksum CHECKSUM("queue_delay_time_ms")
+
+/*
+ * The conveyor holds the queue of blocks, takes care of creating them, and starting the executing chain of blocks
+ *
+ * The Queue is implemented as a ringbuffer- with a twist
+ *
+ * Since delete() is not thread-safe, we must marshall deletable items out of ISR context
+ *
+ * To do this, we have implmented a *double* ringbuffer- two ringbuffers sharing the same ring, and one index pointer
+ *
+ * as in regular ringbuffers, HEAD always points to a clean, free block. We are free to prepare it as we see fit, at our leisure.
+ * When the block is fully prepared, we increment the head pointer, and from that point we must not touch it anymore.
+ *
+ * also, as in regular ringbuffers, we can 'use' the TAIL block, and increment tail pointer when we're finished with it
+ *
+ * Both of these are implemented here- see queue_head_block() (where head is pushed) and on_idle() (where tail is consumed)
+ *
+ * The double ring is implemented by adding a third index pointer that lives in between head and tail. We call it isr_tail_i.
+ *
+ * in ISR context, we use HEAD as the head pointer, and isr_tail_i as the tail pointer.
+ * As HEAD increments, ISR context can consume the new blocks which appear, and when we're finished with a block, we increment isr_tail_i to signal that they're finished, and ready to be cleaned
+ *
+ * in IDLE context, we use isr_tail_i as the head pointer, and TAIL as the tail pointer.
+ * When isr_tail_i != tail, we clean up the tail block (performing ISR-unsafe delete operations) and consume it (increment tail pointer), returning it to the pool of clean, unused blocks which HEAD is allowed to prepare for queueing
+ *
+ * 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.
+ */
 
-// The conveyor holds the queue of blocks, takes care of creating them, and starting the executing chain of blocks
 
-Conveyor::Conveyor(){
-    gc_pending = queue.tail_i;
+Conveyor::Conveyor()
+{
     running = false;
+    halted = false;
+    allow_fetch = false;
+    flush= false;
 }
 
-void Conveyor::on_module_loaded(){
+void Conveyor::on_module_loaded()
+{
     register_for_event(ON_IDLE);
-    register_for_event(ON_CONFIG_RELOAD);
+    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_size = THEKERNEL->config->value(planner_queue_size_checksum)->by_default(32)->as_number();
+    queue_delay_time_ms = THEKERNEL->config->value(queue_delay_time_ms_checksum)->by_default(100)->as_number();
+}
+
+// we allocate the queue here after config is completed so we do not run out of memory during config
+void Conveyor::start(uint8_t n)
+{
+    Block::n_actuators= n; // set the number of motors which determines how big the tick info vector is
+    queue.resize(queue_size);
+    running = true;
+}
+
+void Conveyor::on_halt(void* argument)
+{
+    if(argument == nullptr) {
+        halted = true;
+        flush_queue();
+    } else {
+        halted = false;
+    }
 }
 
-// Delete blocks here, because they can't be deleted in interrupt context ( see Block.cpp:release )
-void Conveyor::on_idle(void* argument){
-    if (queue.tail_i != gc_pending)
-    {
-        if (queue.is_empty())
+void Conveyor::on_idle(void*)
+{
+    if (running) {
+        check_queue();
+    }
+
+    // we can garbage collect the block queue here
+    if (queue.tail_i != queue.isr_tail_i) {
+        if (queue.is_empty()) {
             __debugbreak();
-        else
-        {
+        } else {
             // Cleanly delete block
             Block* block = queue.tail_ref();
-            block->gcodes.clear();
+            //block->debug();
+            block->clear();
             queue.consume_tail();
         }
     }
-    else if (queue.is_empty())
-    {
-        // if someone has appended gcodes but the queue is stopped
-        // make sure they get executed in a timely fashion
-        if (queue.head_ref()->gcodes.size())
-        {
-            queue_head_block();
-            ensure_running();
-        }
-    }
 }
 
-void Conveyor::on_config_reload(void* argument)
+// see if we are idle
+// this checks the block queue is empty, and that the step queue is empty and
+// checks that all motors are no longer moving
+bool Conveyor::is_idle() const
 {
-    queue.resize(THEKERNEL->config->value(planner_queue_size_checksum)->by_default(32)->as_number());
+    if(queue.is_empty()) {
+        for(auto &a : THEROBOT->actuators) {
+            if(a->is_moving()) return false;
+        }
+        return true;
+    }
+
+    return false;
 }
 
-void Conveyor::append_gcode(Gcode* gcode)
+// Wait for the queue to be empty and for all the jobs to finish in step ticker
+void Conveyor::wait_for_idle(bool wait_for_motors)
 {
-    gcode->mark_as_taken();
-    queue.head_ref()->append_gcode(gcode);
+    // wait for the job queue to empty, this means cycling everything on the block queue into the job queue
+    // forcing them to be jobs
+    running = false; // stops on_idle calling check_queue
+    while (!queue.is_empty()) {
+        check_queue(true); // forces queue to be made available to stepticker
+        THEKERNEL->call_event(ON_IDLE, this);
+    }
+
+    if(wait_for_motors) {
+        // now we wait for all motors to stop moving
+        while(!is_idle()) {
+            THEKERNEL->call_event(ON_IDLE, this);
+        }
+    }
+
+    running = true;
+    // returning now means that everything has totally finished
 }
 
-// Process a new block in the queue
-void Conveyor::on_block_end(void* block)
+/*
+ * push the pre-prepared head block onto the queue
+ */
+void Conveyor::queue_head_block()
 {
-    if (queue.is_empty())
-        __debugbreak();
-
-    gc_pending = queue.next(gc_pending);
+    // upstream caller will block on this until there is room in the queue
+    while (queue.is_full() && !halted) {
+        //check_queue();
+        THEKERNEL->call_event(ON_IDLE, this); // will call check_queue();
+    }
 
-    // Return if queue is empty
-    if (gc_pending == queue.head_i)
-    {
-        running = false;
-        return;
+    if(halted) {
+        // we do not want to stick more stuff on the queue if we are in halt state
+        // clear and release the block on the head
+        queue.head_ref()->clear();
+        return; // if we got a halt then we are done here
     }
 
-    // Get a new block
-    Block* next = this->queue.item_ref(gc_pending);
+    queue.produce_head();
 
-    next->begin();
+    // not sure if this is the correct place but we need to turn on the motors if they were not already on
+    THEKERNEL->call_event(ON_ENABLE, (void*)1); // turn all enable pins on
 }
 
-// Wait for the queue to have a given number of free blocks
-void Conveyor::wait_for_queue(int free_blocks)
+void Conveyor::check_queue(bool force)
 {
-    while (queue.is_full())
-    {
-        ensure_running();
-        THEKERNEL->call_event(ON_IDLE);
+    static uint32_t last_time_check = us_ticker_read();
+
+    if(queue.is_empty()) {
+        allow_fetch = false;
+        last_time_check = us_ticker_read(); // reset timeout
+        return;
+    }
+
+    // if we have been waiting for more than the required waiting time and the queue is not empty, or the queue is full, then allow stepticker to get the tail
+    // we do this to allow an idle system to pre load the queue a bit so the first few blocks run smoothly.
+    if(force || queue.is_full() || (us_ticker_read() - last_time_check) >= (queue_delay_time_ms * 1000)) {
+        last_time_check = us_ticker_read(); // reset timeout
+        if(!flush) allow_fetch = true;
+        return;
     }
 }
 
-// Wait for the queue to be empty
-void Conveyor::wait_for_empty_queue()
+// called from step ticker ISR
+bool Conveyor::get_next_block(Block **block)
 {
-    while (!queue.is_empty())
-    {
-        ensure_running();
-        THEKERNEL->call_event(ON_IDLE);
+    // mark entire queue for GC if flush flag is asserted
+    if (flush){
+        while (queue.isr_tail_i != queue.head_i) {
+            queue.isr_tail_i = queue.next(queue.isr_tail_i);
+        }
     }
+
+    // default the feerate to zero if there is no block available
+    this->current_feedrate= 0;
+
+    if(halted || queue.isr_tail_i == queue.head_i) return false; // we do not have anything to give
+
+    // wait for queue to fill up, optimizes planning
+    if(!allow_fetch) return false;
+
+    Block *b= queue.item_ref(queue.isr_tail_i);
+    // we cannot use this now if it is being updated
+    if(!b->locked) {
+        if(!b->is_ready) __debugbreak(); // should never happen
+
+        b->is_ticking= true;
+        b->recalculate_flag= false;
+        this->current_feedrate= b->nominal_speed;
+        *block= b;
+        return true;
+    }
+
+    return false;
 }
 
-// Return true if the queue is empty
-bool Conveyor::is_queue_empty()
+// called from step ticker ISR when block is finished, do not do anything slow here
+void Conveyor::block_finished()
 {
-    return queue.is_empty();
+    // we increment the isr_tail_i so we can get the next block
+    queue.isr_tail_i= queue.next(queue.isr_tail_i);
 }
 
-void Conveyor::queue_head_block()
+/*
+    In most cases this will not totally flush the queue, as when streaming
+    gcode there is one stalled waiting for space in the queue, in
+    queue_head_block() so after this flush, once main_loop runs again one more
+    gcode gets stuck in the queue, this is bad. Current work around is to call
+    this when the queue in not full and streaming has stopped
+*/
+void Conveyor::flush_queue()
 {
-    while (queue.is_full())
-    {
-        ensure_running();
-        THEKERNEL->call_event(ON_IDLE, this);
-    }
+    allow_fetch = false;
+    flush= true;
 
-    queue.produce_head();
-    ensure_running();
-}
+    // TODO force deceleration of last block
 
-void Conveyor::ensure_running()
-{
-    if (!running)
-    {
-        running = true;
-        queue.tail_ref()->begin();
-    }
+    // now wait until the block queue has been flushed
+    wait_for_idle(false);
+
+    flush= false;
 }
 
 // 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();