Conveyor: add verbose commentary
[clinton/Smoothieware.git] / src / modules / robot / Conveyor.cpp
CommitLineData
f80d18b9
L
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
8using namespace std;
9#include <vector>
10#include "libs/nuts_bolts.h"
11#include "libs/RingBuffer.h"
12#include "../communication/utils/Gcode.h"
13#include "libs/Module.h"
14#include "libs/Kernel.h"
15#include "Timer.h" // mbed.h lib
16#include "wait_api.h" // mbed.h lib
17#include "Block.h"
18#include "Conveyor.h"
19#include "Planner.h"
55456577 20#include "mri.h"
f80d18b9 21
0b3e628f
MM
22#define planner_queue_size_checksum CHECKSUM("planner_queue_size")
23
8698e81a
MM
24/*
25 * The conveyor holds the queue of blocks, takes care of creating them, and starting the executing chain of blocks
26 *
27 * The Queue is implemented as a ringbuffer- with a twist
28 *
29 * Since delete() is not thread-safe, we must marshall deletable items out of ISR context
30 *
31 * To do this, we have implmented a *double* ringbuffer- two ringbuffers sharing the same ring, and one index pointer
32 *
33 * 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.
34 * When the block is fully prepared, we increment the head pointer, and from that point we must not touch it anymore.
35 *
36 * also, as in regular ringbuffers, we can 'use' the TAIL block, and increment tail pointer when we're finished with it
37 *
38 * Both of these are implemented here- see queue_head_block() (where head is pushed) and on_idle() (where tail is consumed)
39 *
40 * The double ring is implemented by adding a third index pointer that lives in between head and tail. We call it gc_pending which describes its function rather than its operation
41 *
42 * in ISR context, we use HEAD as the head pointer, and gc_pending as the tail pointer.
43 * As HEAD increments, ISR context can consume the new blocks which appear, and when we're finished with a block, we increment gc_pending to signal that they're finishd, and ready to be cleaned
44 *
45 * in IDLE context, we use gc_pending as the head pointer, and TAIL as the tail pointer.
46 * When gc_pending != 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
47 *
48 * 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.
49 */
edac9072 50
f80d18b9 51Conveyor::Conveyor(){
c501670b 52 gc_pending = queue.tail_i;
2134bcf2 53 running = false;
702023f3
MM
54}
55
d149c730 56void Conveyor::on_module_loaded(){
702023f3 57 register_for_event(ON_IDLE);
01b69353 58 register_for_event(ON_MAIN_LOOP);
0b3e628f
MM
59 register_for_event(ON_CONFIG_RELOAD);
60
61 on_config_reload(this);
702023f3
MM
62}
63
edac9072 64// Delete blocks here, because they can't be deleted in interrupt context ( see Block.cpp:release )
8698e81a 65// note that blocks get cleaned as they come off the tail, so head ALWAYS points to a cleaned block.
d149c730 66void Conveyor::on_idle(void* argument){
55456577 67 if (queue.tail_i != gc_pending)
c501670b 68 {
55456577
MM
69 if (queue.is_empty())
70 __debugbreak();
71 else
72 {
73 // Cleanly delete block
74 Block* block = queue.tail_ref();
9d005957 75// block->debug();
3ac0b99e 76 block->clear();
55456577
MM
77 queue.consume_tail();
78 }
702023f3 79 }
01b69353
MM
80}
81
8698e81a
MM
82/*
83 * In on_main_loop, we check whether the queue should be running, but isn't.
84 *
85 * The main trigger for this event is other pieces of code adding gcode to a block, but not pushing it. This occurs frequently with gcodes that must be executed at the correct point in the queue, but take zero time to execute.
86 * Smoothie will happily attach many of such gcodes onto a single block, to save room in the queue.
87 *
88 * Any gcode which can potentially take time to execute, or might like to halt the queue MUST push the head block, otherwise gcodes that arrive later may get executed at the same time, and gcode execution order strictness would be violated.
89 *
90 * If we get back to main loop context and the block has gcode but isn't pushed, then we can safely push it and start the queue.
91 *
92 *
93 * It's also theoretically possible that a race condition could occur where we pop the final block and stop the queue, while at the same time main loop is pushing head but thinks the queue is running and thus does not start it.
94 *
95 * In this case, we start the queue again when execution returns to main loop.
96 * No stuttering or other visible effects could be caused by this event, as the planner will have set the last block to decelerate to zero, and the new block to accelerate from zero.
97 *
98 */
99
01b69353
MM
100void Conveyor::on_main_loop(void*)
101{
102 if (running)
103 return;
104
105 if (queue.is_empty())
3facc890 106 {
3facc890
MM
107 if (queue.head_ref()->gcodes.size())
108 {
109 queue_head_block();
110 ensure_running();
111 }
112 }
36aca284
MM
113 else
114 // queue not empty
115 ensure_running();
f80d18b9
L
116}
117
0b3e628f
MM
118void Conveyor::on_config_reload(void* argument)
119{
120 queue.resize(THEKERNEL->config->value(planner_queue_size_checksum)->by_default(32)->as_number());
121}
122
e0ee24ed
MM
123void Conveyor::append_gcode(Gcode* gcode)
124{
125 gcode->mark_as_taken();
c87f8e07 126 queue.head_ref()->append_gcode(gcode);
e0ee24ed
MM
127}
128
f80d18b9 129// Process a new block in the queue
2134bcf2
MM
130void Conveyor::on_block_end(void* block)
131{
55456577
MM
132 if (queue.is_empty())
133 __debugbreak();
0b3e628f 134
2134bcf2 135 gc_pending = queue.next(gc_pending);
f80d18b9
L
136
137 // Return if queue is empty
2134bcf2
MM
138 if (gc_pending == queue.head_i)
139 {
140 running = false;
f80d18b9
L
141 return;
142 }
702023f3 143
f80d18b9 144 // Get a new block
2134bcf2 145 Block* next = this->queue.item_ref(gc_pending);
f80d18b9 146
2134bcf2 147 next->begin();
f80d18b9
L
148}
149
edac9072 150// Wait for the queue to have a given number of free blocks
c501670b
MM
151void Conveyor::wait_for_queue(int free_blocks)
152{
153 while (queue.is_full())
2134bcf2
MM
154 {
155 ensure_running();
314ab8f7 156 THEKERNEL->call_event(ON_IDLE);
2134bcf2 157 }
f80d18b9 158}
17c68379 159
edac9072 160// Wait for the queue to be empty
c501670b
MM
161void Conveyor::wait_for_empty_queue()
162{
163 while (!queue.is_empty())
2134bcf2
MM
164 {
165 ensure_running();
314ab8f7 166 THEKERNEL->call_event(ON_IDLE);
2134bcf2 167 }
17c68379
BG
168}
169
68d16168 170// Return true if the queue is empty
c501670b
MM
171bool Conveyor::is_queue_empty()
172{
173 return queue.is_empty();
68d16168
L
174}
175
8698e81a
MM
176/*
177 * push the pre-prepared head block onto the queue
178 */
2134bcf2
MM
179void Conveyor::queue_head_block()
180{
181 while (queue.is_full())
182 {
183 ensure_running();
184 THEKERNEL->call_event(ON_IDLE, this);
185 }
186
9d005957 187 queue.head_ref()->ready();
2134bcf2
MM
188 queue.produce_head();
189}
190
191void Conveyor::ensure_running()
192{
193 if (!running)
194 {
56a3ab89
MM
195 if (gc_pending == queue.head_i)
196 return;
197
2134bcf2 198 running = true;
56a3ab89 199 queue.item_ref(gc_pending)->begin();
2134bcf2
MM
200 }
201}
c501670b 202
a617ac35
MM
203// Debug function
204void Conveyor::dump_queue()
205{
206 for (unsigned int index = queue.tail_i, i = 0; true; index = queue.next(index), i++ )
207 {
208 THEKERNEL->streams->printf("block %03d > ", i);
209 queue.item_ref(index)->debug();
210
211 if (index == queue.head_i)
212 break;
213 }
214}
215
c501670b
MM
216// feels hacky, but apparently the way to do it
217#include "HeapRing.cpp"
218template class HeapRing<Block>;