Merge remote-tracking branch 'upstream/edge' into upstream-master
[clinton/Smoothieware.git] / src / modules / tools / filamentdetector / FilamentDetector.cpp
1
2 /*
3 Handles a filament detector that has an optical encoder wheel, that generates pulses as the filament
4 moves through it.
5 It also supports a "bulge" detector that triggers if the filament has a bulge in it
6 */
7
8 #include "FilamentDetector.h"
9 #include "Kernel.h"
10 #include "Config.h"
11 #include "checksumm.h"
12 #include "ConfigValue.h"
13 #include "SlowTicker.h"
14 #include "PublicData.h"
15 #include "StreamOutputPool.h"
16 #include "StreamOutput.h"
17 #include "SerialMessage.h"
18 #include "FilamentDetector.h"
19 #include "utils.h"
20 #include "Gcode.h"
21
22 #include "InterruptIn.h" // mbed
23 #include "us_ticker_api.h" // mbed
24
25 #define extruder_checksum CHECKSUM("extruder")
26
27 #define filament_detector_checksum CHECKSUM("filament_detector")
28 #define enable_checksum CHECKSUM("enable")
29 #define encoder_pin_checksum CHECKSUM("encoder_pin")
30 #define bulge_pin_checksum CHECKSUM("bulge_pin")
31 #define seconds_per_check_checksum CHECKSUM("seconds_per_check")
32 #define pulses_per_mm_checksum CHECKSUM("pulses_per_mm")
33
34 FilamentDetector::FilamentDetector()
35 {
36 suspended= false;
37 filament_out_alarm= false;
38 bulge_detected= false;
39 active= true;
40 e_last_moved= NAN;
41 }
42
43 FilamentDetector::~FilamentDetector()
44 {
45 if(encoder_pin != nullptr) delete encoder_pin;
46 }
47
48 void FilamentDetector::on_module_loaded()
49 {
50 // if the module is disabled -> do nothing
51 if(!THEKERNEL->config->value( filament_detector_checksum, enable_checksum )->by_default(false)->as_bool()) {
52 // as this module is not needed free up the resource
53 delete this;
54 return;
55 }
56
57 // encoder pin has to be interrupt enabled pin like 0.26, 0.27, 0.28
58 Pin dummy_pin;
59 dummy_pin.from_string( THEKERNEL->config->value(filament_detector_checksum, encoder_pin_checksum)->by_default("nc" )->as_string());
60 this->encoder_pin= dummy_pin.interrupt_pin();
61
62 // optional bulge detector
63 bulge_pin.from_string( THEKERNEL->config->value(filament_detector_checksum, bulge_pin_checksum)->by_default("nc" )->as_string())->as_input();
64 if(bulge_pin.connected()) {
65 // input pin polling
66 THEKERNEL->slow_ticker->attach( 100, this, &FilamentDetector::button_tick);
67 }
68
69 //Valid configurations contain an encoder pin, a bulge pin or both.
70 //free the module if not a valid configuration
71 if(this->encoder_pin == nullptr && !bulge_pin.connected()) {
72 delete this;
73 return;
74 }
75
76 //only monitor the encoder if we are using the encodeer.
77 if (this->encoder_pin != nullptr) {
78 // set interrupt on rising edge
79 this->encoder_pin->rise(this, &FilamentDetector::on_pin_rise);
80 NVIC_SetPriority(EINT3_IRQn, 16); // set to low priority
81 }
82
83
84 // how many seconds between checks, must be long enough for several pulses to be detected, but not too long
85 seconds_per_check= THEKERNEL->config->value(filament_detector_checksum, seconds_per_check_checksum)->by_default(2)->as_number();
86
87 // the number of pulses per mm of filament moving through the detector, can be fractional
88 pulses_per_mm= THEKERNEL->config->value(filament_detector_checksum, pulses_per_mm_checksum)->by_default(1)->as_number();
89
90 // register event-handlers
91 if (this->encoder_pin != nullptr) {
92 //This event is only valid if we are using the encodeer.
93 register_for_event(ON_SECOND_TICK);
94 }
95
96 register_for_event(ON_MAIN_LOOP);
97 register_for_event(ON_CONSOLE_LINE_RECEIVED);
98 this->register_for_event(ON_GCODE_RECEIVED);
99 }
100
101
102 void FilamentDetector::send_command(std::string msg, StreamOutput *stream)
103 {
104 struct SerialMessage message;
105 message.message = msg;
106 message.stream = stream;
107 THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message );
108 }
109
110 // needed to detect when we resume
111 void FilamentDetector::on_console_line_received( void *argument )
112 {
113 if(!suspended) return;
114
115 SerialMessage new_message = *static_cast<SerialMessage *>(argument);
116 string possible_command = new_message.message;
117 string cmd = shift_parameter(possible_command);
118 if(cmd == "resume") {
119 this->pulses= 0;
120 e_last_moved= NAN;
121 suspended= false;
122 }
123 }
124
125 float FilamentDetector::get_emove()
126 {
127 float *rd;
128 if(PublicData::get_value( extruder_checksum, (void **)&rd )) {
129 return *(rd+5); // current position for extruder in mm
130 }
131 return NAN;
132 }
133
134 void FilamentDetector::on_gcode_received(void *argument)
135 {
136 Gcode *gcode = static_cast<Gcode *>(argument);
137 if (gcode->has_m) {
138 if (gcode->m == 404) { // set filament detector parameters S seconds per check, P pulses per mm
139 if(gcode->has_letter('S')){
140 seconds_per_check= gcode->get_value('S');
141 seconds_passed= 0;
142 }
143 if(gcode->has_letter('P')){
144 pulses_per_mm= gcode->get_value('P');
145 }
146 gcode->stream->printf("// pulses per mm: %f, seconds per check: %d\n", pulses_per_mm, seconds_per_check);
147
148 } else if (gcode->m == 405) { // disable filament detector
149 active= false;
150 e_last_moved= get_emove();
151
152 }else if (gcode->m == 406) { // enable filament detector
153 this->pulses= 0;
154 e_last_moved= get_emove();
155 active= true;
156
157 }else if (gcode->m == 407) { // display filament detector pulses and status
158 float e_moved= get_emove();
159 if(!isnan(e_moved)) {
160 float delta= e_moved - e_last_moved;
161 gcode->stream->printf("Extruder moved: %f mm\n", delta);
162 }
163
164 gcode->stream->printf("Encoder pulses: %u\n", pulses.load());
165 if(this->suspended) gcode->stream->printf("Filament detector triggered\n");
166 gcode->stream->printf("Filament detector is %s\n", active?"enabled":"disabled");
167 }
168 }
169 }
170
171 void FilamentDetector::on_main_loop(void *argument)
172 {
173 if (active && this->filament_out_alarm) {
174 this->filament_out_alarm = false;
175 if(bulge_detected){
176 THEKERNEL->streams->printf("// Filament Detector has detected a bulge in the filament\n");
177 bulge_detected= false;
178 }else{
179 THEKERNEL->streams->printf("// Filament Detector has detected a filament jam\n");
180 }
181
182 if(!suspended) {
183 this->suspended= true;
184 // fire suspend command
185 this->send_command( "M600", &(StreamOutput::NullStream) );
186 }
187 }
188 }
189
190 void FilamentDetector::on_second_tick(void *argument)
191 {
192 if(++seconds_passed >= seconds_per_check) {
193 seconds_passed= 0;
194 check_encoder();
195 }
196 }
197
198 // encoder pin interrupt
199 void FilamentDetector::on_pin_rise()
200 {
201 this->pulses++;
202 }
203
204 void FilamentDetector::check_encoder()
205 {
206 if(suspended) return; // already suspended
207 if(!active) return; // not enabled
208
209 uint32_t pulse_cnt= this->pulses.exchange(0); // atomic load and reset
210
211 // get number of E steps taken and make sure we have seen enough pulses to cover that
212 float e_moved= get_emove();
213 if(isnan(e_last_moved)) {
214 e_last_moved= e_moved;
215 return;
216 }
217
218 float delta= e_moved - e_last_moved;
219 e_last_moved= e_moved;
220 if(delta < 0) {
221 // we ignore retracts for the purposes of jam detection
222 return;
223 }
224
225 // figure out how many pulses need to have happened to cover that e move
226 uint32_t needed_pulses= floorf(delta*pulses_per_mm);
227 // NOTE if needed_pulses is 0 then extruder did not move since last check, or not enough to register
228 if(needed_pulses == 0) return;
229
230 if(pulse_cnt == 0) {
231 // we got no pulses and E moved since last time so fire off alarm
232 this->filament_out_alarm= true;
233 }
234 }
235
236 uint32_t FilamentDetector::button_tick(uint32_t dummy)
237 {
238 if(!bulge_pin.connected() || suspended || !active) return 0;
239
240 if(bulge_pin.get()) {
241 // we got a trigger from the bulge detector
242 this->filament_out_alarm= true;
243 this->bulge_detected= true;
244 }
245
246 return 0;
247 }