added Mxxx commands to control filament detector
[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 active= true;
39 e_last_moved= NAN;
40 }
41
42 FilamentDetector::~FilamentDetector()
43 {
44 if(encoder_pin != nullptr) delete encoder_pin;
45 }
46
47 void FilamentDetector::on_module_loaded()
48 {
49 // if the module is disabled -> do nothing
50 if(!THEKERNEL->config->value( filament_detector_checksum, enable_checksum )->by_default(false)->as_bool()) {
51 // as this module is not needed free up the resource
52 delete this;
53 return;
54 }
55
56 // encoder pin has to be interrupt enabled pin like 0.26, 0.27, 0.28
57 Pin dummy_pin;
58 dummy_pin.from_string( THEKERNEL->config->value(filament_detector_checksum, encoder_pin_checksum)->by_default("nc" )->as_string());
59 this->encoder_pin= dummy_pin.interrupt_pin();
60 if(this->encoder_pin == nullptr) {
61 // was not a valid interrupt pin
62 delete this;
63 return;
64 }
65
66 // set interrupt on rising edge
67 this->encoder_pin->rise(this, &FilamentDetector::on_pin_rise);
68 NVIC_SetPriority(EINT3_IRQn, 16); // set to low priority
69
70 // optional bulge detector
71 bulge_pin.from_string( THEKERNEL->config->value(filament_detector_checksum, bulge_pin_checksum)->by_default("nc" )->as_string())->as_input();
72 if(bulge_pin.connected()) {
73 // input pin polling
74 THEKERNEL->slow_ticker->attach( 100, this, &FilamentDetector::button_tick);
75 }
76
77 // how many seconds between checks, must be long enough for several pulses to be detected, but not too long
78 seconds_per_check= THEKERNEL->config->value(filament_detector_checksum, seconds_per_check_checksum)->by_default(2)->as_number();
79
80 // the number of pulses per mm of filament moving through the detector, can be fractional
81 pulses_per_mm= THEKERNEL->config->value(filament_detector_checksum, pulses_per_mm_checksum)->by_default(1)->as_number();
82
83 // register event-handlers
84 register_for_event(ON_SECOND_TICK);
85 register_for_event(ON_MAIN_LOOP);
86 register_for_event(ON_CONSOLE_LINE_RECEIVED);
87 this->register_for_event(ON_GCODE_RECEIVED);
88 }
89
90 void FilamentDetector::send_command(std::string msg, StreamOutput *stream)
91 {
92 struct SerialMessage message;
93 message.message = msg;
94 message.stream = stream;
95 THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message );
96 }
97
98 // needed to detect when we resume
99 void FilamentDetector::on_console_line_received( void *argument )
100 {
101 if(!suspended) return;
102
103 SerialMessage new_message = *static_cast<SerialMessage *>(argument);
104 string possible_command = new_message.message;
105 string cmd = shift_parameter(possible_command);
106 if(cmd == "resume") {
107 suspended= false;
108 }
109 }
110
111 void FilamentDetector::on_gcode_received(void *argument)
112 {
113 Gcode *gcode = static_cast<Gcode *>(argument);
114 if (gcode->has_m) {
115 if (gcode->m == 405) { // diable filament detector
116 active= false;
117
118 }else if (gcode->m == 406) { // enable filament detector
119 this->pulses= 0;
120 e_last_moved= NAN;
121 active= true;
122
123 }else if (gcode->m == 407) { // display filament detector pulses and status
124 float *rd;
125 if(!PublicData::get_value( extruder_checksum, (void **)&rd )) {
126 float e_moved= *(rd+5); // current position for extruder in mm
127 float delta= e_moved - e_last_moved;
128 gcode->stream->printf("Extruder moved: %f mm\n", delta);
129 }
130
131 gcode->stream->printf("Encoder pulses: %u\n", pulses.load());
132 if(this->suspended) gcode->stream->printf("Filament detector triggered\n");
133 gcode->stream->printf("Filament detector is %s\n", active?"enabled":"disabled");
134 }
135 }
136 }
137
138 void FilamentDetector::on_main_loop(void *argument)
139 {
140 if (active && this->filament_out_alarm) {
141 this->filament_out_alarm = false;
142 THEKERNEL->streams->printf("// Filament Detector has detected a filament jam\n");
143 this->suspended= true;
144 // fire suspend command
145 this->send_command( "M600", &(StreamOutput::NullStream) );
146 }
147 }
148
149 void FilamentDetector::on_second_tick(void *argument)
150 {
151 if(++seconds_passed >= seconds_per_check) {
152 seconds_passed= 0;
153 check_encoder();
154 }
155 }
156
157 // encoder pin interrupt
158 void FilamentDetector::on_pin_rise()
159 {
160 this->pulses++;
161 }
162
163 void FilamentDetector::check_encoder()
164 {
165 uint32_t pulse_cnt= this->pulses.exchange(0); // atomic load and reset
166 if(suspended) return; // already suspended
167
168 // get number of E steps taken and make sure we have seen enough pulses to cover that
169 float *rd;
170 if(!PublicData::get_value( extruder_checksum, (void **)&rd )) return;
171 float e_moved= *(rd+5); // current position for extruder in mm
172 if(isnan(e_last_moved)) {
173 e_last_moved= e_moved;
174 return;
175 }
176
177 float delta= e_moved - e_last_moved;
178 e_last_moved= e_moved;
179
180 // figure out how many pulses need to have happened to cover that e move
181 uint32_t needed_pulses= floorf(delta*pulses_per_mm);
182 // NOTE if needed_pulses is 0 then extruder did not move since last check, or not enough to register
183 if(needed_pulses == 0) return;
184
185 if(pulse_cnt == 0) {
186 // we got no pulses and E moved since last time so fire off alarm
187 this->filament_out_alarm= true;
188 }
189 }
190
191 uint32_t FilamentDetector::button_tick(uint32_t dummy)
192 {
193 if(!bulge_pin.connected()) return 0;
194
195 if(bulge_pin.get()) {
196 // we got a trigger from the bulge detector
197 this->filament_out_alarm= true;
198 }
199
200 return 0;
201 }