3 Handles a filament detector that has an optical encoder wheel, that generates pulses as the filament
5 It also supports a "bulge" detector that triggers if the filament has a bulge in it
8 #include "FilamentDetector.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"
22 #include "InterruptIn.h" // mbed
23 #include "us_ticker_api.h" // mbed
25 #define extruder_checksum CHECKSUM("extruder")
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")
34 FilamentDetector::FilamentDetector()
37 filament_out_alarm
= false;
38 bulge_detected
= false;
43 FilamentDetector::~FilamentDetector()
45 if(encoder_pin
!= nullptr) delete encoder_pin
;
48 void FilamentDetector::on_module_loaded()
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
57 // encoder pin has to be interrupt enabled pin like 0.26, 0.27, 0.28
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();
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()) {
66 THEKERNEL
->slow_ticker
->attach( 100, this, &FilamentDetector::button_tick
);
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()) {
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
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();
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();
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
);
96 register_for_event(ON_MAIN_LOOP
);
97 register_for_event(ON_CONSOLE_LINE_RECEIVED
);
98 this->register_for_event(ON_GCODE_RECEIVED
);
102 void FilamentDetector::send_command(std::string msg
, StreamOutput
*stream
)
104 struct SerialMessage message
;
105 message
.message
= msg
;
106 message
.stream
= stream
;
107 THEKERNEL
->call_event(ON_CONSOLE_LINE_RECEIVED
, &message
);
110 // needed to detect when we resume
111 void FilamentDetector::on_console_line_received( void *argument
)
113 if(!suspended
) return;
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") {
125 float FilamentDetector::get_emove()
128 if(PublicData::get_value( extruder_checksum
, (void **)&rd
)) {
129 return *(rd
+5); // current position for extruder in mm
134 void FilamentDetector::on_gcode_received(void *argument
)
136 Gcode
*gcode
= static_cast<Gcode
*>(argument
);
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');
143 if(gcode
->has_letter('P')){
144 pulses_per_mm
= gcode
->get_value('P');
146 gcode
->stream
->printf("// pulses per mm: %f, seconds per check: %d\n", pulses_per_mm
, seconds_per_check
);
148 } else if (gcode
->m
== 405) { // disable filament detector
150 e_last_moved
= get_emove();
152 }else if (gcode
->m
== 406) { // enable filament detector
154 e_last_moved
= get_emove();
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
);
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");
171 void FilamentDetector::on_main_loop(void *argument
)
173 if (active
&& this->filament_out_alarm
) {
174 this->filament_out_alarm
= false;
176 THEKERNEL
->streams
->printf("// Filament Detector has detected a bulge in the filament\n");
177 bulge_detected
= false;
179 THEKERNEL
->streams
->printf("// Filament Detector has detected a filament jam\n");
183 this->suspended
= true;
184 // fire suspend command
185 this->send_command( "M600", &(StreamOutput::NullStream
) );
190 void FilamentDetector::on_second_tick(void *argument
)
192 if(++seconds_passed
>= seconds_per_check
) {
198 // encoder pin interrupt
199 void FilamentDetector::on_pin_rise()
204 void FilamentDetector::check_encoder()
206 if(suspended
) return; // already suspended
207 if(!active
) return; // not enabled
209 uint32_t pulse_cnt
= this->pulses
.exchange(0); // atomic load and reset
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
;
218 float delta
= e_moved
- e_last_moved
;
219 e_last_moved
= e_moved
;
221 // we ignore retracts for the purposes of jam detection
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;
231 // we got no pulses and E moved since last time so fire off alarm
232 this->filament_out_alarm
= true;
236 uint32_t FilamentDetector::button_tick(uint32_t dummy
)
238 if(!bulge_pin
.connected() || suspended
|| !active
) return 0;
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;