Merge pull request #1294 from wolfmanjm/upstreamedge
[clinton/Smoothieware.git] / src / modules / robot / Conveyor.cpp
1 /*
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)
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.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8 #include "nuts_bolts.h"
9 #include "Gcode.h"
10 #include "Module.h"
11 #include "Kernel.h"
12 #include "Timer.h" // mbed.h lib
13 #include "wait_api.h" // mbed.h lib
14 #include "Block.h"
15 #include "Conveyor.h"
16 #include "Planner.h"
17 #include "mri.h"
18 #include "checksumm.h"
19 #include "Config.h"
20 #include "StreamOutputPool.h"
21 #include "ConfigValue.h"
22 #include "StepTicker.h"
23 #include "Robot.h"
24 #include "StepperMotor.h"
25
26 #include <functional>
27 #include <vector>
28
29 #include "mbed.h"
30
31 #define planner_queue_size_checksum CHECKSUM("planner_queue_size")
32 #define queue_delay_time_ms_checksum CHECKSUM("queue_delay_time_ms")
33
34 /*
35 * The conveyor holds the queue of blocks, takes care of creating them, and starting the executing chain of blocks
36 *
37 * The Queue is implemented as a ringbuffer- with a twist
38 *
39 * Since delete() is not thread-safe, we must marshall deletable items out of ISR context
40 *
41 * To do this, we have implmented a *double* ringbuffer- two ringbuffers sharing the same ring, and one index pointer
42 *
43 * 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.
44 * When the block is fully prepared, we increment the head pointer, and from that point we must not touch it anymore.
45 *
46 * also, as in regular ringbuffers, we can 'use' the TAIL block, and increment tail pointer when we're finished with it
47 *
48 * Both of these are implemented here- see queue_head_block() (where head is pushed) and on_idle() (where tail is consumed)
49 *
50 * The double ring is implemented by adding a third index pointer that lives in between head and tail. We call it isr_tail_i.
51 *
52 * in ISR context, we use HEAD as the head pointer, and isr_tail_i as the tail pointer.
53 * 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
54 *
55 * in IDLE context, we use isr_tail_i as the head pointer, and TAIL as the tail pointer.
56 * 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
57 *
58 * 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.
59 */
60
61
62 Conveyor::Conveyor()
63 {
64 running = false;
65 allow_fetch = false;
66 flush= false;
67 }
68
69 void Conveyor::on_module_loaded()
70 {
71 register_for_event(ON_IDLE);
72 register_for_event(ON_HALT);
73
74 // Attach to the end_of_move stepper event
75 //THEKERNEL->step_ticker->finished_fnc = std::bind( &Conveyor::all_moves_finished, this);
76 queue_size = THEKERNEL->config->value(planner_queue_size_checksum)->by_default(32)->as_number();
77 queue_delay_time_ms = THEKERNEL->config->value(queue_delay_time_ms_checksum)->by_default(100)->as_number();
78 }
79
80 // we allocate the queue here after config is completed so we do not run out of memory during config
81 void Conveyor::start(uint8_t n)
82 {
83 Block::init(n); // set the number of motors which determines how big the tick info vector is
84 queue.resize(queue_size);
85 running = true;
86 }
87
88 void Conveyor::on_halt(void* argument)
89 {
90 if(argument == nullptr) {
91 flush_queue();
92 }
93 }
94
95 void Conveyor::on_idle(void*)
96 {
97 if (running) {
98 check_queue();
99 }
100
101 // we can garbage collect the block queue here
102 if (queue.tail_i != queue.isr_tail_i) {
103 if (queue.is_empty()) {
104 __debugbreak();
105 } else {
106 // Cleanly delete block
107 Block* block = queue.tail_ref();
108 //block->debug();
109 block->clear();
110 queue.consume_tail();
111 }
112 }
113 }
114
115 // see if we are idle
116 // this checks the block queue is empty, and that the step queue is empty and
117 // checks that all motors are no longer moving
118 bool Conveyor::is_idle() const
119 {
120 if(queue.is_empty()) {
121 for(auto &a : THEROBOT->actuators) {
122 if(a->is_moving()) return false;
123 }
124 return true;
125 }
126
127 return false;
128 }
129
130 // Wait for the queue to be empty and for all the jobs to finish in step ticker
131 void Conveyor::wait_for_idle(bool wait_for_motors)
132 {
133 // wait for the job queue to empty, this means cycling everything on the block queue into the job queue
134 // forcing them to be jobs
135 running = false; // stops on_idle calling check_queue
136 while (!queue.is_empty()) {
137 check_queue(true); // forces queue to be made available to stepticker
138 THEKERNEL->call_event(ON_IDLE, this);
139 }
140
141 if(wait_for_motors) {
142 // now we wait for all motors to stop moving
143 while(!is_idle()) {
144 THEKERNEL->call_event(ON_IDLE, this);
145 }
146 }
147
148 running = true;
149 // returning now means that everything has totally finished
150 }
151
152 /*
153 * push the pre-prepared head block onto the queue
154 */
155 void Conveyor::queue_head_block()
156 {
157 // upstream caller will block on this until there is room in the queue
158 while (queue.is_full() && !THEKERNEL->is_halted()) {
159 //check_queue();
160 THEKERNEL->call_event(ON_IDLE, this); // will call check_queue();
161 }
162
163 if(THEKERNEL->is_halted()) {
164 // we do not want to stick more stuff on the queue if we are in halt state
165 // clear and release the block on the head
166 queue.head_ref()->clear();
167 return; // if we got a halt then we are done here
168 }
169
170 queue.produce_head();
171
172 // not sure if this is the correct place but we need to turn on the motors if they were not already on
173 THEKERNEL->call_event(ON_ENABLE, (void*)1); // turn all enable pins on
174 }
175
176 void Conveyor::check_queue(bool force)
177 {
178 static uint32_t last_time_check = us_ticker_read();
179
180 if(queue.is_empty()) {
181 allow_fetch = false;
182 last_time_check = us_ticker_read(); // reset timeout
183 return;
184 }
185
186 // 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
187 // we do this to allow an idle system to pre load the queue a bit so the first few blocks run smoothly.
188 if(force || queue.is_full() || (us_ticker_read() - last_time_check) >= (queue_delay_time_ms * 1000)) {
189 last_time_check = us_ticker_read(); // reset timeout
190 if(!flush) allow_fetch = true;
191 return;
192 }
193 }
194
195 // called from step ticker ISR
196 bool Conveyor::get_next_block(Block **block)
197 {
198 // mark entire queue for GC if flush flag is asserted
199 if (flush){
200 while (queue.isr_tail_i != queue.head_i) {
201 queue.isr_tail_i = queue.next(queue.isr_tail_i);
202 }
203 }
204
205 // default the feerate to zero if there is no block available
206 this->current_feedrate= 0;
207
208 if(THEKERNEL->is_halted() || queue.isr_tail_i == queue.head_i) return false; // we do not have anything to give
209
210 // wait for queue to fill up, optimizes planning
211 if(!allow_fetch) return false;
212
213 Block *b= queue.item_ref(queue.isr_tail_i);
214 // we cannot use this now if it is being updated
215 if(!b->locked) {
216 if(!b->is_ready) __debugbreak(); // should never happen
217
218 b->is_ticking= true;
219 b->recalculate_flag= false;
220 this->current_feedrate= b->nominal_speed;
221 *block= b;
222 return true;
223 }
224
225 return false;
226 }
227
228 // called from step ticker ISR when block is finished, do not do anything slow here
229 void Conveyor::block_finished()
230 {
231 // we increment the isr_tail_i so we can get the next block
232 queue.isr_tail_i= queue.next(queue.isr_tail_i);
233 }
234
235 /*
236 In most cases this will not totally flush the queue, as when streaming
237 gcode there is one stalled waiting for space in the queue, in
238 queue_head_block() so after this flush, once main_loop runs again one more
239 gcode gets stuck in the queue, this is bad. Current work around is to call
240 this when the queue in not full and streaming has stopped
241 */
242 void Conveyor::flush_queue()
243 {
244 allow_fetch = false;
245 flush= true;
246
247 // TODO force deceleration of last block
248
249 // now wait until the block queue has been flushed
250 wait_for_idle(false);
251
252 flush= false;
253 }
254
255 // Debug function
256 void Conveyor::dump_queue()
257 {
258 for (unsigned int index = queue.tail_i, i = 0; true; index = queue.next(index), i++ ) {
259 THEKERNEL->streams->printf("block %03d > ", i);
260 queue.item_ref(index)->debug();
261
262 if (index == queue.head_i)
263 break;
264 }
265 }